From 8ed1c2a6f5ba278b65636c7f4e87fa6da2c85270 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 05:19:06 +0000 Subject: [PATCH 01/70] build(deps): bump chainguard/static from `fd59d10` to `17c4607` Bumps chainguard/static from `fd59d10` to `17c4607`. --- updated-dependencies: - dependency-name: chainguard/static dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index fe8a98860..9f4eb48c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ 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" From 73c804ebdb29031c7ea35cf96446bca2faab7288 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 15:42:55 +0000 Subject: [PATCH 02/70] build(deps): bump golang.org/x/sync from 0.5.0 to 0.6.0 Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.5.0 to 0.6.0. - [Commits](https://github.com/golang/sync/compare/v0.5.0...v0.6.0) --- updated-dependencies: - dependency-name: golang.org/x/sync dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 616258940..29f4ec73f 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,7 @@ require ( 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/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 diff --git a/go.sum b/go.sum index 4d5fefbfb..b89100501 100644 --- a/go.sum +++ b/go.sum @@ -645,8 +645,8 @@ 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.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= From 81e51f28ea87385420bf4d3773cfd223d99d130c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 15:43:00 +0000 Subject: [PATCH 03/70] build(deps): bump github.com/envoyproxy/protoc-gen-validate Bumps [github.com/envoyproxy/protoc-gen-validate](https://github.com/envoyproxy/protoc-gen-validate) from 1.0.2 to 1.0.4. - [Release notes](https://github.com/envoyproxy/protoc-gen-validate/releases) - [Changelog](https://github.com/bufbuild/protoc-gen-validate/blob/main/.goreleaser.yaml) - [Commits](https://github.com/envoyproxy/protoc-gen-validate/compare/v1.0.2...v1.0.4) --- updated-dependencies: - dependency-name: github.com/envoyproxy/protoc-gen-validate dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 616258940..174dbd0d8 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( 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/golang-jwt/jwt/v4 v4.5.0 diff --git a/go.sum b/go.sum index 4d5fefbfb..314a43df8 100644 --- a/go.sum +++ b/go.sum @@ -116,8 +116,8 @@ 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/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= From 3a13954b70067e84631fa2598c82ea280baff389 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 15:43:01 +0000 Subject: [PATCH 04/70] build(deps): bump github.com/jackc/pgtype from 1.14.0 to 1.14.2 Bumps [github.com/jackc/pgtype](https://github.com/jackc/pgtype) from 1.14.0 to 1.14.2. - [Changelog](https://github.com/jackc/pgtype/blob/master/CHANGELOG.md) - [Commits](https://github.com/jackc/pgtype/compare/v1.14.0...v1.14.2) --- updated-dependencies: - dependency-name: github.com/jackc/pgtype dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 616258940..8c86fc67c 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ 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/pgtype v1.14.2 github.com/jackc/pgx/v5 v5.5.1 github.com/juju/ratelimit v1.0.2 github.com/onsi/ginkgo/v2 v2.13.2 diff --git a/go.sum b/go.sum index 4d5fefbfb..fb07b5b40 100644 --- a/go.sum +++ b/go.sum @@ -290,8 +290,8 @@ 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= From d7ef586d0799baff36678a256e25063ea03c235b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 05:47:47 +0000 Subject: [PATCH 05/70] build(deps): bump github/codeql-action from 3.24.0 to 3.24.5 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.24.0 to 3.24.5. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/e8893c57a1f3a2b659b6b55564fdfdbbd2982911...47b3d888fe66b639e431abf22ebca059152f1eea) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecard.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bd1549dad..ced089161 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@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 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@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 # ℹī¸ 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@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index e4f1134df..27621efc2 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@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 with: sarif_file: results.sarif From 93b9d24a3fd71ee5b7e24329044775151188a399 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:15:21 +0000 Subject: [PATCH 06/70] build(deps): bump github.com/opencontainers/runc from 1.1.7 to 1.1.12 Bumps [github.com/opencontainers/runc](https://github.com/opencontainers/runc) from 1.1.7 to 1.1.12. - [Release notes](https://github.com/opencontainers/runc/releases) - [Changelog](https://github.com/opencontainers/runc/blob/v1.1.12/CHANGELOG.md) - [Commits](https://github.com/opencontainers/runc/compare/v1.1.7...v1.1.12) --- updated-dependencies: - dependency-name: github.com/opencontainers/runc dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 44e5e64d8..7fb714aa4 100644 --- a/go.mod +++ b/go.mod @@ -115,7 +115,7 @@ require ( 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/runc v1.1.12 // 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 diff --git a/go.sum b/go.sum index befbd4576..014f6b020 100644 --- a/go.sum +++ b/go.sum @@ -373,8 +373,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 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/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= +github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= 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= From fc2b05f8e4f3189f35177712d94a946a8b6af2d0 Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Mon, 26 Feb 2024 22:29:13 +0300 Subject: [PATCH 07/70] refactor(config): improve Permify config command structure for clarity --- pkg/cmd/config.go | 185 +++++++++++++++++++++++++++++------------ pkg/cmd/flags/serve.go | 2 +- 2 files changed, 134 insertions(+), 53 deletions(-) diff --git a/pkg/cmd/config.go b/pkg/cmd/config.go index 802ffdd69..3217fc56a 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,153 @@ 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", 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", - } - - 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] + "***" - } +// 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)) } - return value + // 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]) } diff --git a/pkg/cmd/flags/serve.go b/pkg/cmd/flags/serve.go index 17a57f7b4..3dfd506d5 100644 --- a/pkg/cmd/flags/serve.go +++ b/pkg/cmd/flags/serve.go @@ -439,7 +439,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) From 613498e807df7fbdd33d4fbccab4e82d7f7b0195 Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Mon, 26 Feb 2024 22:30:11 +0300 Subject: [PATCH 08/70] build: version info update --- docs/apidocs.swagger.json | 2 +- internal/info.go | 2 +- pkg/pb/base/v1/openapi.pb.go | 2 +- proto/base/v1/openapi.proto | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/apidocs.swagger.json b/docs/apidocs.swagger.json index ac38c267b..95ef58480 100644 --- a/docs/apidocs.swagger.json +++ b/docs/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.7", "contact": { "name": "API Support", "url": "https://github.com/Permify/permify/issues", diff --git a/internal/info.go b/internal/info.go index 70aa4f228..67f680113 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.7" ) // Function to create a single line of the ASCII art with centered content and color diff --git a/pkg/pb/base/v1/openapi.pb.go b/pkg/pb/base/v1/openapi.pb.go index c6e842532..936a5be82 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, 0x37, 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/proto/base/v1/openapi.proto b/proto/base/v1/openapi.proto index ca7b292a5..2328db138 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.7"; contact: { name: "API Support"; url: "https://github.com/Permify/permify/issues"; From 1256c47a77bd5801dc99ab30e8e71c7a51b72d69 Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Tue, 27 Feb 2024 11:50:15 +0300 Subject: [PATCH 09/70] feat(config): mask pre-shared keys in output --- pkg/cmd/config.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/config.go b/pkg/cmd/config.go index 3217fc56a..9307bfbf2 100644 --- a/pkg/cmd/config.go +++ b/pkg/cmd/config.go @@ -77,7 +77,7 @@ func conf() func(cmd *cobra.Command, args []string) error { // 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", cfg.Authn.Preshared.Keys), getKeyOrigin(cmd, "authn-preshared-keys", "PERMIFY_AUTHN_PRESHARED_KEYS")}, + []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 @@ -177,3 +177,12 @@ func HideSecret(secret string) string { // 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]) } + +// 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 +} From 0b8e8173e1b3278fc89e5554a4a165444bf631a2 Mon Sep 17 00:00:00 2001 From: EgeAytin Date: Tue, 27 Feb 2024 14:06:41 +0300 Subject: [PATCH 10/70] docs: add read & list schema endpoins to sidebar --- docs/sidebars.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/sidebars.js b/docs/sidebars.js index c6a301750..7ebddc495 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -83,7 +83,9 @@ module.exports = { slug: "/api-overview/schema", }, items: [ - "api-overview/schema/write-schema" + "api-overview/schema/write-schema", + "api-overview/schema/read-schema", + "api-overview/schema/list-schema" ], }, { From b775a12d09051c62309dfe72bb47c4f6c1c74e0a Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Tue, 27 Feb 2024 17:39:43 +0300 Subject: [PATCH 11/70] refactor: move span.RecordError to 'else' block for non-context errors --- internal/storage/postgres/utils/common.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/internal/storage/postgres/utils/common.go b/internal/storage/postgres/utils/common.go index a8291d78c..8d95bc3c5 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)) From f80b8e8579a3386ab0d81aaaf64744d39d754210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Bergstro=CC=88m?= Date: Tue, 27 Feb 2024 11:44:22 -0300 Subject: [PATCH 12/70] chore(docs): fix otpl typo --- docs/docs/reference/configuration.md | 2 +- docs/versioned_docs/version-0.2.x/installation/container.md | 2 +- docs/versioned_docs/version-0.3.x/reference/configuration.md | 2 +- docs/versioned_docs/version-0.4.x/reference/configuration.md | 2 +- docs/versioned_docs/version-0.5.x/reference/configuration.md | 2 +- docs/versioned_docs/version-0.6.x/reference/configuration.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/docs/reference/configuration.md b/docs/docs/reference/configuration.md index 184366ab3..36fa44401 100644 --- a/docs/docs/reference/configuration.md +++ b/docs/docs/reference/configuration.md @@ -344,7 +344,7 @@ os, arch. | Required | Argument | Default | Description | |----------|----------|---------|--------------------------------------------------------------| -| [x] | exporter | - | [otpl](https://opentelemetry.io/docs/collector/) is default. | +| [x] | exporter | - | [otlp](https://opentelemetry.io/docs/collector/) is default. | | [x] | endpoint | - | export uri for metric observation | | [ ] | enabled | true | switch option for meter tracing. | diff --git a/docs/versioned_docs/version-0.2.x/installation/container.md b/docs/versioned_docs/version-0.2.x/installation/container.md index c4a6c0c3a..c8cd09a7e 100644 --- a/docs/versioned_docs/version-0.2.x/installation/container.md +++ b/docs/versioned_docs/version-0.2.x/installation/container.md @@ -92,7 +92,7 @@ database: * **enabled:** switch option for tracing. *(default: false)* * **meter** (optional) - * **exporter:** [otpl](https://opentelemetry.io/docs/collector/) is default. + * **exporter:** [otlp](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)* diff --git a/docs/versioned_docs/version-0.3.x/reference/configuration.md b/docs/versioned_docs/version-0.3.x/reference/configuration.md index 205e59f13..21ca4553c 100644 --- a/docs/versioned_docs/version-0.3.x/reference/configuration.md +++ b/docs/versioned_docs/version-0.3.x/reference/configuration.md @@ -234,7 +234,7 @@ Configuration for observing metrics; check count, cache check count and session | Required | Argument | Default | Description | |----------|----------|---------|---------| -| [x] | exporter | - | [otpl](https://opentelemetry.io/docs/collector/) is default. +| [x] | exporter | - | [otlp](https://opentelemetry.io/docs/collector/) is default. | [x] | endpoint | - | export uri for metric observation | | [ ] | enabled | true | switch option for meter tracing. diff --git a/docs/versioned_docs/version-0.4.x/reference/configuration.md b/docs/versioned_docs/version-0.4.x/reference/configuration.md index bda87c612..3572f52f6 100644 --- a/docs/versioned_docs/version-0.4.x/reference/configuration.md +++ b/docs/versioned_docs/version-0.4.x/reference/configuration.md @@ -305,7 +305,7 @@ os, arch. | Required | Argument | Default | Description | |----------|----------|---------|--------------------------------------------------------------| -| [x] | exporter | - | [otpl](https://opentelemetry.io/docs/collector/) is default. | +| [x] | exporter | - | [otlp](https://opentelemetry.io/docs/collector/) is default. | | [x] | endpoint | - | export uri for metric observation | | [ ] | enabled | true | switch option for meter tracing. | diff --git a/docs/versioned_docs/version-0.5.x/reference/configuration.md b/docs/versioned_docs/version-0.5.x/reference/configuration.md index df94202bb..97bd9ce0a 100644 --- a/docs/versioned_docs/version-0.5.x/reference/configuration.md +++ b/docs/versioned_docs/version-0.5.x/reference/configuration.md @@ -337,7 +337,7 @@ os, arch. | Required | Argument | Default | Description | |----------|----------|---------|--------------------------------------------------------------| -| [x] | exporter | - | [otpl](https://opentelemetry.io/docs/collector/) is default. | +| [x] | exporter | - | [otlp](https://opentelemetry.io/docs/collector/) is default. | | [x] | endpoint | - | export uri for metric observation | | [ ] | enabled | true | switch option for meter tracing. | diff --git a/docs/versioned_docs/version-0.6.x/reference/configuration.md b/docs/versioned_docs/version-0.6.x/reference/configuration.md index 184366ab3..36fa44401 100644 --- a/docs/versioned_docs/version-0.6.x/reference/configuration.md +++ b/docs/versioned_docs/version-0.6.x/reference/configuration.md @@ -344,7 +344,7 @@ os, arch. | Required | Argument | Default | Description | |----------|----------|---------|--------------------------------------------------------------| -| [x] | exporter | - | [otpl](https://opentelemetry.io/docs/collector/) is default. | +| [x] | exporter | - | [otlp](https://opentelemetry.io/docs/collector/) is default. | | [x] | endpoint | - | export uri for metric observation | | [ ] | enabled | true | switch option for meter tracing. | From 7e2a76f45b9eebe9cb94286f00de901a2e91bec1 Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Tue, 27 Feb 2024 21:23:46 +0300 Subject: [PATCH 13/70] fix: ensure JWKsURI is set in OIDC configuration --- internal/authn/oidc/authn.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/authn/oidc/authn.go b/internal/authn/oidc/authn.go index b02519a1f..08ff327fb 100644 --- a/internal/authn/oidc/authn.go +++ b/internal/authn/oidc/authn.go @@ -45,7 +45,7 @@ type Authn struct { // 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) { +func NewOidcAuthn(_ context.Context, conf 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. client := retryablehttp.NewClient() @@ -54,8 +54,8 @@ func NewOidcAuthn(_ context.Context, audience config.Oidc) (*Authn, error) { // 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. oidc := &Authn{ - IssuerURL: audience.Issuer, - Audience: audience.Audience, + IssuerURL: conf.Issuer, + Audience: conf.Audience, httpClient: client.StandardClient(), // Wrap retryable client as a standard http.Client } @@ -226,6 +226,7 @@ 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. From 6f3735a55d7a9ae1b7b3e69fd2a4199a24d96a87 Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Tue, 27 Feb 2024 22:12:41 +0300 Subject: [PATCH 14/70] docs: add testing config instructions --- docs/docs/reference/configuration.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/docs/reference/configuration.md b/docs/docs/reference/configuration.md index 2c45935aa..70af81a9c 100644 --- a/docs/docs/reference/configuration.md +++ b/docs/docs/reference/configuration.md @@ -548,6 +548,13 @@ improving scalability and performance in distributed systems."

+ +## Testing Config + +You can use the `permify config` command to view the latest state of your configuration. The `Value` part represents the configuration that will be executed, and the `Source` part indicates where this value comes from. There are three options for the "Source" part: (`ENV`, `FILE`, `FLAG`) + +![testing-configuration](https://github.com/Permify/permify/assets/39353278/1b376e81-1fb1-4ccb-b379-a66226d272a2) + [jaeger]: https://www.jaegertracing.io/ [otlp]: https://opentelemetry.io/ From 40367bb7a9ba06849db30fccb403ed622d898de4 Mon Sep 17 00:00:00 2001 From: neo773 <62795688+neo773@users.noreply.github.com> Date: Wed, 28 Feb 2024 02:07:41 +0530 Subject: [PATCH 15/70] fix: #1092 --- pkg/cmd/serve.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/cmd/serve.go b/pkg/cmd/serve.go index 220e93fc7..214f38904 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) @@ -145,6 +148,8 @@ func serve() func(cmd *cobra.Command, args []string) error { db, err := factories.DatabaseFactory(cfg.Database) if err != nil { slog.Error("failed to initialize database", slog.Any("error", err)) + // Add the new log message here + slog.Info("Giving up from reconnecting and exiting.") return err } defer func() { From 48c9b021cb972f3a2bb1454591cbc0601f5770d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 05:26:43 +0000 Subject: [PATCH 16/70] build(deps): bump docker/setup-buildx-action from 3.0.0 to 3.1.0 Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/f95db51fddba0c2d1ec667646a06c2ce06100226...0d103c3126aa41d772a8362f6aa67afac040f80c) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/nightly.yaml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 200224b74..77d50a74e 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -38,7 +38,7 @@ jobs: - 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@0d103c3126aa41d772a8362f6aa67afac040f80c # v3.1.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 c4f45f58d..6f839b026 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,7 @@ jobs: - 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@0d103c3126aa41d772a8362f6aa67afac040f80c # v3.1.0 - name: Run GoReleaser uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 with: From 9c79d75c37e630be49edcdec85499e469d7c83c6 Mon Sep 17 00:00:00 2001 From: neo773 <62795688+neo773@users.noreply.github.com> Date: Wed, 28 Feb 2024 19:53:19 +0530 Subject: [PATCH 17/70] remove redundant log --- pkg/cmd/serve.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/cmd/serve.go b/pkg/cmd/serve.go index 214f38904..d4ecf6b78 100644 --- a/pkg/cmd/serve.go +++ b/pkg/cmd/serve.go @@ -148,8 +148,6 @@ func serve() func(cmd *cobra.Command, args []string) error { db, err := factories.DatabaseFactory(cfg.Database) if err != nil { slog.Error("failed to initialize database", slog.Any("error", err)) - // Add the new log message here - slog.Info("Giving up from reconnecting and exiting.") return err } defer func() { From 8c9839187c3ed2f0879c83600a800d0a8379bebf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 05:10:00 +0000 Subject: [PATCH 18/70] build(deps): bump golang from 1.21-alpine to 1.22-alpine Bumps golang from 1.21-alpine to 1.22-alpine. --- updated-dependencies: - dependency-name: golang dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- Dockerfile | 2 +- Dockerfile.local | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index fe8a98860..d17d9b727 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # 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 . . diff --git a/Dockerfile.local b/Dockerfile.local index 2b6759c89..6ffc17096 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 From 3f089ee17bef5aac10df0a555f2919ab7f3da670 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 05:50:04 +0000 Subject: [PATCH 19/70] build(deps): bump github/codeql-action from 3.24.5 to 3.24.6 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.24.5 to 3.24.6. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/47b3d888fe66b639e431abf22ebca059152f1eea...8a470fddafa5cbb6266ee11b37ef4d8aae19c571) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecard.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ced089161..05bf4d914 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@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 + uses: github/codeql-action/init@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 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@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 + uses: github/codeql-action/autobuild@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 # ℹī¸ 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@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 + uses: github/codeql-action/analyze@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 27621efc2..980fd0b82 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@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 + uses: github/codeql-action/upload-sarif@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 with: sarif_file: results.sarif From 95e98610e66a9a33fcc94a9789fa41ec4595a9e0 Mon Sep 17 00:00:00 2001 From: Tolga Ozen <39353278+tolgaOzen@users.noreply.github.com> Date: Mon, 4 Mar 2024 21:18:48 +0300 Subject: [PATCH 20/70] chore(sync): synchronize with permify pro repo --- .github/workflows/sync.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/sync.yml diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml new file mode 100644 index 000000000..1acb32dda --- /dev/null +++ b/.github/workflows/sync.yml @@ -0,0 +1,21 @@ +name: Sync to Permify Pro + +on: + push: + branches: + - master + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Checkout source repository + uses: actions/checkout@v2 + + - name: Push changes to the destination repository + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GH_TOKEN }} + repository: Permify/permify-pro + branch: sync + force: true From 0063c5214d4423220ba57adb09552f338245a21f Mon Sep 17 00:00:00 2001 From: Tolga Ozen <39353278+tolgaOzen@users.noreply.github.com> Date: Wed, 6 Mar 2024 19:05:59 +0300 Subject: [PATCH 21/70] feat: sync with upstream action --- .github/workflows/sync.yml | 49 ++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 1acb32dda..6c31c4562 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -1,21 +1,44 @@ name: Sync to Permify Pro - on: - push: - branches: - - master + schedule: + - cron: '0 7 * * 1,4' + workflow_dispatch: jobs: - sync: + sync_latest_from_upstream: runs-on: ubuntu-latest + name: Sync latest commits from upstream repo + steps: - - name: Checkout source repository - uses: actions/checkout@v2 + # REQUIRED step + # Step 1: run a standard checkout action, provided by github + - name: Checkout target repo + uses: actions/checkout@v3 + with: + ref: master + persist-credentials: false - - name: Push changes to the destination repository - uses: ad-m/github-push-action@master + # REQUIRED step + # Step 2: run the sync action + - name: Sync upstream changes + id: sync + uses: aormsby/Fork-Sync-With-Upstream-action@v3.4 with: - github_token: ${{ secrets.GH_TOKEN }} - repository: Permify/permify-pro - branch: sync - force: true + target_sync_branch: master + target_repo_token: ${{ secrets.GH_TOKEN }} + upstream_sync_branch: sync + upstream_sync_repo: Permify/permify-pro + upstream_repo_access_token: ${{ secrets.GH_TOKEN }} + test_mode: true + + # Step 3: Display a sample message based on the sync output var 'has_new_commits' + - name: New commits found + if: steps.sync.outputs.has_new_commits == 'true' + run: echo "New commits were found to sync." + + - name: No new commits + if: steps.sync.outputs.has_new_commits == 'false' + run: echo "There were no new commits." + + - name: Show value of 'has_new_commits' + run: echo ${{ steps.sync.outputs.has_new_commits }} From 856a4e464c7fc7bd1c418c33ce43a470c51406d7 Mon Sep 17 00:00:00 2001 From: Tolga Ozen <39353278+tolgaOzen@users.noreply.github.com> Date: Wed, 6 Mar 2024 19:26:36 +0300 Subject: [PATCH 22/70] ci: test mode disabled --- .github/workflows/sync.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 6c31c4562..0fb03bd0b 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -29,7 +29,7 @@ jobs: upstream_sync_branch: sync upstream_sync_repo: Permify/permify-pro upstream_repo_access_token: ${{ secrets.GH_TOKEN }} - test_mode: true + test_mode: false # Step 3: Display a sample message based on the sync output var 'has_new_commits' - name: New commits found From e4cdde0263ed482ac945c7f9fed1b1f6196e74e7 Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Wed, 6 Mar 2024 20:33:51 +0300 Subject: [PATCH 23/70] feat: repo-file-sync-action added --- .github/sync.yml | 5 ++++ .github/workflows/sync.yml | 48 ++++++++------------------------------ 2 files changed, 15 insertions(+), 38 deletions(-) create mode 100644 .github/sync.yml diff --git a/.github/sync.yml b/.github/sync.yml new file mode 100644 index 000000000..3070e54b3 --- /dev/null +++ b/.github/sync.yml @@ -0,0 +1,5 @@ +Permify/permify-pro@sync: + - source: go.mod + dest: go.mod + - source: go.sum + dest: go.sum \ No newline at end of file diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 0fb03bd0b..07c21f080 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -1,44 +1,16 @@ name: Sync to Permify Pro on: - schedule: - - cron: '0 7 * * 1,4' + push: + branches: + - master workflow_dispatch: - jobs: - sync_latest_from_upstream: + sync: runs-on: ubuntu-latest - name: Sync latest commits from upstream repo - steps: - # REQUIRED step - # Step 1: run a standard checkout action, provided by github - - name: Checkout target repo - uses: actions/checkout@v3 - with: - ref: master - persist-credentials: false - - # REQUIRED step - # Step 2: run the sync action - - name: Sync upstream changes - id: sync - uses: aormsby/Fork-Sync-With-Upstream-action@v3.4 - with: - target_sync_branch: master - target_repo_token: ${{ secrets.GH_TOKEN }} - upstream_sync_branch: sync - upstream_sync_repo: Permify/permify-pro - upstream_repo_access_token: ${{ secrets.GH_TOKEN }} - test_mode: false - - # Step 3: Display a sample message based on the sync output var 'has_new_commits' - - name: New commits found - if: steps.sync.outputs.has_new_commits == 'true' - run: echo "New commits were found to sync." - - - name: No new commits - if: steps.sync.outputs.has_new_commits == 'false' - run: echo "There were no new commits." - - - name: Show value of 'has_new_commits' - run: echo ${{ steps.sync.outputs.has_new_commits }} + - name: Checkout Repository + uses: actions/checkout@master + - name: Run GitHub File Sync + uses: BetaHuhn/repo-file-sync-action@v1 + with: + GH_PAT: ${{ secrets.GH_TOKEN }} \ No newline at end of file From 2dbc1d5a1a7ff7a2c92be476ffc711c80dd126c6 Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Thu, 7 Mar 2024 11:41:40 +0300 Subject: [PATCH 24/70] chore(sync): synchronize latest changes to permify-pro --- .github/sync.yml | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/sync.yml b/.github/sync.yml index 3070e54b3..ebf268c58 100644 --- a/.github/sync.yml +++ b/.github/sync.yml @@ -1,5 +1,21 @@ -Permify/permify-pro@sync: +Permify/permify-pro: - source: go.mod dest: go.mod - source: go.sum - dest: go.sum \ No newline at end of file + dest: go.sum + - source: cmd/ + dest: cmd/ + - source: docs/ + dest: docs/ + - source: integration-test/ + dest: integration-test/ + - source: internal/ + dest: internal/ + - source: pkg/ + dest: pkg/ + - source: playground/ + dest: playground/ + - source: proto/ + dest: proto/ + - source: tools/ + dest: tools/ \ No newline at end of file From e5fff7f15b634ae7a16eef09f49ef92b6969def8 Mon Sep 17 00:00:00 2001 From: StepSecurity Bot Date: Thu, 7 Mar 2024 08:53:13 +0000 Subject: [PATCH 25/70] [StepSecurity] ci: Harden GitHub Actions Signed-off-by: StepSecurity Bot --- .github/workflows/sync.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 07c21f080..64035d351 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -8,9 +8,14 @@ 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@master + uses: actions/checkout@61b9e3751b92087fd0b06925ba6dd6314e06f089 # master - name: Run GitHub File Sync - uses: BetaHuhn/repo-file-sync-action@v1 + uses: BetaHuhn/repo-file-sync-action@3023dac7ce66c18b119e2012348437eadeaea116 # v1.21.0 with: GH_PAT: ${{ secrets.GH_TOKEN }} \ No newline at end of file From 6525dc9f7cb21a798d5a2a205f066efa02f85235 Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Thu, 7 Mar 2024 12:32:27 +0300 Subject: [PATCH 26/70] chore(sync): remove 'docs' and 'playground' from sync process --- .github/sync.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/sync.yml b/.github/sync.yml index ebf268c58..778859db5 100644 --- a/.github/sync.yml +++ b/.github/sync.yml @@ -5,16 +5,12 @@ Permify/permify-pro: dest: go.sum - source: cmd/ dest: cmd/ - - source: docs/ - dest: docs/ - source: integration-test/ dest: integration-test/ - source: internal/ dest: internal/ - source: pkg/ dest: pkg/ - - source: playground/ - dest: playground/ - source: proto/ dest: proto/ - source: tools/ From 27c16de9575f9d5274c7155ee6b3229dc590d739 Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Thu, 7 Mar 2024 15:29:16 +0300 Subject: [PATCH 27/70] ci: exclude info.go from internal --- .github/sync.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/sync.yml b/.github/sync.yml index 778859db5..3b2680ead 100644 --- a/.github/sync.yml +++ b/.github/sync.yml @@ -9,6 +9,8 @@ Permify/permify-pro: dest: integration-test/ - source: internal/ dest: internal/ + exclude: | + info.go - source: pkg/ dest: pkg/ - source: proto/ From fc79d5f2687efd2e8c1d770c8e0fb5e45779be00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Mar 2024 22:59:21 +0000 Subject: [PATCH 28/70] build(deps): bump github.com/go-jose/go-jose/v3 from 3.0.1 to 3.0.3 Bumps [github.com/go-jose/go-jose/v3](https://github.com/go-jose/go-jose) from 3.0.1 to 3.0.3. - [Release notes](https://github.com/go-jose/go-jose/releases) - [Changelog](https://github.com/go-jose/go-jose/blob/v3.0.3/CHANGELOG.md) - [Commits](https://github.com/go-jose/go-jose/compare/v3.0.1...v3.0.3) --- updated-dependencies: - dependency-name: github.com/go-jose/go-jose/v3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 33 ++++++++++++++++++++++++++------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 1520766ce..ba4a3f36b 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/envoyproxy/protoc-gen-validate v1.0.2 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 @@ -143,9 +143,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/crypto v0.19.0 // indirect golang.org/x/mod v0.13.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.14.0 // indirect google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 // indirect diff --git a/go.sum b/go.sum index 2bf26e008..70b8f9679 100644 --- a/go.sum +++ b/go.sum @@ -127,8 +127,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= @@ -481,6 +481,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= @@ -546,16 +547,16 @@ 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 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 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,6 +592,8 @@ 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.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.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -626,6 +629,9 @@ 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.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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -649,6 +655,8 @@ 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.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= @@ -697,18 +705,25 @@ 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 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.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/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 +732,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,6 +796,8 @@ 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.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= From 871da8b8d8213a07f54bd4727407e669fdc42673 Mon Sep 17 00:00:00 2001 From: wolf-hash Date: Sun, 10 Mar 2024 23:31:07 +0530 Subject: [PATCH 29/70] feat: python sdk generator --- .github/workflows/sdk-generator.yml | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/sdk-generator.yml diff --git a/.github/workflows/sdk-generator.yml b/.github/workflows/sdk-generator.yml new file mode 100644 index 000000000..d4f5fcdfb --- /dev/null +++ b/.github/workflows/sdk-generator.yml @@ -0,0 +1,37 @@ +name: Generate Client SDKs from OpenAPI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Generate Python Client + uses: openapi-generators/openapitools-generator-action@v1 + with: + generator: python + openapi-file: docs/apidocs.swagger.json + + - name: Push Python SDK to GitHub + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + TARGET_REPO: "permify/permify-python" + run: | + git config --global user.name 'GitHub Actions Bot' + git config --global user.email '<>' + git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${TARGET_REPO}.git + git checkout -b main + rm -rf !python-sdk + git add python-sdk + git commit -m "Update Python SDK from OpenAPI changes" + git push origin main --force From d85886f115ba16fad6d40843c9f24f17c608716d Mon Sep 17 00:00:00 2001 From: wolf-hash Date: Sun, 10 Mar 2024 23:34:19 +0530 Subject: [PATCH 30/70] fix: client folder name --- .github/workflows/sdk-generator.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sdk-generator.yml b/.github/workflows/sdk-generator.yml index d4f5fcdfb..245d34f93 100644 --- a/.github/workflows/sdk-generator.yml +++ b/.github/workflows/sdk-generator.yml @@ -31,7 +31,7 @@ jobs: git config --global user.email '<>' git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${TARGET_REPO}.git git checkout -b main - rm -rf !python-sdk - git add python-sdk + rm -rf !python-client + git add python-client git commit -m "Update Python SDK from OpenAPI changes" git push origin main --force From dc5269c7cdd17140cfeb47e792f9735aefa79a46 Mon Sep 17 00:00:00 2001 From: wolf-hash Date: Sun, 10 Mar 2024 23:38:32 +0530 Subject: [PATCH 31/70] test: sdk generator --- .github/workflows/sdk-generator.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sdk-generator.yml b/.github/workflows/sdk-generator.yml index 245d34f93..4e9d0deb1 100644 --- a/.github/workflows/sdk-generator.yml +++ b/.github/workflows/sdk-generator.yml @@ -7,6 +7,7 @@ on: pull_request: branches: - main + - sdk-generator jobs: build: From bde8c7dee7f190facddad79633a204c3fc2ebe80 Mon Sep 17 00:00:00 2001 From: wolf-hash Date: Sun, 10 Mar 2024 23:39:37 +0530 Subject: [PATCH 32/70] chore: cleanup --- .github/workflows/sdk-generator.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/sdk-generator.yml b/.github/workflows/sdk-generator.yml index 4e9d0deb1..245d34f93 100644 --- a/.github/workflows/sdk-generator.yml +++ b/.github/workflows/sdk-generator.yml @@ -7,7 +7,6 @@ on: pull_request: branches: - main - - sdk-generator jobs: build: From 912e23c1d259b2f50050e8ab287cc7ac0f6ac9fc Mon Sep 17 00:00:00 2001 From: wolf-hash Date: Mon, 11 Mar 2024 11:29:27 +0530 Subject: [PATCH 33/70] fix: master branch --- .github/workflows/sdk-generator.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sdk-generator.yml b/.github/workflows/sdk-generator.yml index 245d34f93..29f52c4de 100644 --- a/.github/workflows/sdk-generator.yml +++ b/.github/workflows/sdk-generator.yml @@ -3,10 +3,11 @@ name: Generate Client SDKs from OpenAPI on: push: branches: - - main + - master pull_request: branches: - - main + - master + workflow_dispatch: jobs: build: From 4098dad87975a0e26e5f1f39c04f5d1f61df16e7 Mon Sep 17 00:00:00 2001 From: wolf-hash Date: Mon, 11 Mar 2024 11:58:15 +0530 Subject: [PATCH 34/70] fix: github token secret name --- .github/workflows/sdk-generator.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sdk-generator.yml b/.github/workflows/sdk-generator.yml index 29f52c4de..f4f9c329c 100644 --- a/.github/workflows/sdk-generator.yml +++ b/.github/workflows/sdk-generator.yml @@ -25,7 +25,7 @@ jobs: - name: Push Python SDK to GitHub env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.SDK_GH_TOKEN }} TARGET_REPO: "permify/permify-python" run: | git config --global user.name 'GitHub Actions Bot' From e41f66adc8a3acca246cb55db4b16dff519495b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 05:30:45 +0000 Subject: [PATCH 35/70] build(deps): bump github/codeql-action from 3.24.6 to 3.24.7 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.24.6 to 3.24.7. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/8a470fddafa5cbb6266ee11b37ef4d8aae19c571...3ab4101902695724f9365a384f86c1074d94e18c) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecard.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 05bf4d914..1142fdae4 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@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/init@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 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@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/autobuild@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 # ℹī¸ 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@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/analyze@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 980fd0b82..6ea6c196f 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@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/upload-sarif@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 with: sarif_file: results.sarif From fbd29d1fb134e60f57d12d19f1a224eb57ff8a7a Mon Sep 17 00:00:00 2001 From: thegeekywanderer Date: Wed, 13 Mar 2024 11:24:15 +0530 Subject: [PATCH 36/70] fix: pushing to remote --- .github/workflows/sdk-generator.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/sdk-generator.yml b/.github/workflows/sdk-generator.yml index f4f9c329c..77f6fa747 100644 --- a/.github/workflows/sdk-generator.yml +++ b/.github/workflows/sdk-generator.yml @@ -30,9 +30,9 @@ jobs: run: | git config --global user.name 'GitHub Actions Bot' git config --global user.email '<>' - git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${TARGET_REPO}.git - git checkout -b main - rm -rf !python-client - git add python-client + git clone https://${GITHUB_TOKEN}@github.com/${TARGET_REPO}.git temp + cp -r python-client/* temp/ + cd temp + git add . git commit -m "Update Python SDK from OpenAPI changes" - git push origin main --force + git push https://${GITHUB_TOKEN}@github.com/${TARGET_REPO}.git main --force From b522bfeefffba63f60715d186eb14d049fa7d287 Mon Sep 17 00:00:00 2001 From: thegeekywanderer Date: Wed, 13 Mar 2024 20:34:04 +0530 Subject: [PATCH 37/70] feat: javascript sdk generator --- .github/workflows/sdk-generator.yml | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/sdk-generator.yml b/.github/workflows/sdk-generator.yml index 77f6fa747..d266bb6f0 100644 --- a/.github/workflows/sdk-generator.yml +++ b/.github/workflows/sdk-generator.yml @@ -12,6 +12,14 @@ on: jobs: build: runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.SDK_GH_TOKEN }} + ORG_NAME: permify + SWAGGER_PATH: docs/apidocs.swagger.json + + strategy: + matrix: + language: [python, javascript] steps: - name: Checkout repository @@ -20,19 +28,18 @@ jobs: - name: Generate Python Client uses: openapi-generators/openapitools-generator-action@v1 with: - generator: python - openapi-file: docs/apidocs.swagger.json + 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 Python SDK to GitHub - env: - GITHUB_TOKEN: ${{ secrets.SDK_GH_TOKEN }} - TARGET_REPO: "permify/permify-python" + - 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/${TARGET_REPO}.git temp - cp -r python-client/* temp/ + git clone https://${GITHUB_TOKEN}@github.com/${ORG_NAME}/permify-${{ matrix.language }}.git temp + cp -r permify-client/* temp/ cd temp git add . - git commit -m "Update Python SDK from OpenAPI changes" - git push https://${GITHUB_TOKEN}@github.com/${TARGET_REPO}.git main --force + 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 From 090cf19504277165f9817107b6ffff90a293936d Mon Sep 17 00:00:00 2001 From: thegeekywanderer Date: Wed, 13 Mar 2024 21:03:10 +0530 Subject: [PATCH 38/70] fix: empty commit --- .github/workflows/sdk-generator.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sdk-generator.yml b/.github/workflows/sdk-generator.yml index d266bb6f0..8425fc1d1 100644 --- a/.github/workflows/sdk-generator.yml +++ b/.github/workflows/sdk-generator.yml @@ -40,6 +40,6 @@ jobs: cp -r permify-client/* temp/ cd temp git add . - git commit -m "Update ${{ matrix.language }} SDK from OpenAPI changes" + 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 From a46b64cd05366d4e189bd4420870ddd5e5d83418 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 23:33:44 +0000 Subject: [PATCH 39/70] build(deps): bump google.golang.org/protobuf from 1.32.0 to 1.33.0 Bumps google.golang.org/protobuf from 1.32.0 to 1.33.0. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1520766ce..69710601e 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( 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 ) diff --git a/go.sum b/go.sum index 2bf26e008..3af0dc19d 100644 --- a/go.sum +++ b/go.sum @@ -888,8 +888,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= From b644d8e10f57b44e164344f49a784323512dbccb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Mar 2024 05:28:00 +0000 Subject: [PATCH 40/70] build(deps): bump docker/login-action from 3.0.0 to 3.1.0 Bumps [docker/login-action](https://github.com/docker/login-action) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/343f7c4344506bcbf9b4de18042ae17996df046d...e92390c5fb421da1463c202d546fed0ec5c39f20) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/nightly.yaml | 4 ++-- .github/workflows/release.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 77d50a74e..3a3f67d6d 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -25,13 +25,13 @@ 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 }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6f839b026..e4825920b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,13 +25,13 @@ 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 }} From bbb083c1d4d27892588bbc9d75ef20099e49f62c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Mar 2024 10:52:25 +0000 Subject: [PATCH 41/70] build(deps): bump github.com/google/cel-go from 0.18.2 to 0.20.1 Bumps [github.com/google/cel-go](https://github.com/google/cel-go) from 0.18.2 to 0.20.1. - [Release notes](https://github.com/google/cel-go/releases) - [Commits](https://github.com/google/cel-go/compare/v0.18.2...v0.20.1) --- updated-dependencies: - dependency-name: github.com/google/cel-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ba4a3f36b..8a2bb5910 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( 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 diff --git a/go.sum b/go.sum index 70b8f9679..47854ee65 100644 --- a/go.sum +++ b/go.sum @@ -179,8 +179,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= From 459785b7dd18919595b45e4047c87ca7eeafa329 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 05:35:13 +0000 Subject: [PATCH 42/70] build(deps): bump docker/setup-buildx-action from 3.1.0 to 3.2.0 Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/0d103c3126aa41d772a8362f6aa67afac040f80c...2b51285047da1547ffb1b2203d8be4c0af6b1f20) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/nightly.yaml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 3a3f67d6d..96e6575a4 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -38,7 +38,7 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name : Set up Docker Buildx - uses: docker/setup-buildx-action@0d103c3126aa41d772a8362f6aa67afac040f80c # v3.1.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 e4825920b..61cc6a35c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,7 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@0d103c3126aa41d772a8362f6aa67afac040f80c # v3.1.0 + uses: docker/setup-buildx-action@2b51285047da1547ffb1b2203d8be4c0af6b1f20 # v3.2.0 - name: Run GoReleaser uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 with: From 4dcac924be11b4c63cd6d4b3628c4c8b26982feb Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Fri, 15 Mar 2024 16:43:44 +0300 Subject: [PATCH 43/70] feat(service-proto): update API description and add code samples --- docs/apidocs.swagger.json | 273 +++++ pkg/pb/base/v1/service.pb.go | 1896 +++++++++++++++++++++++++++++----- proto/base/v1/service.proto | 842 ++++++++++++++- 3 files changed, 2753 insertions(+), 258 deletions(-) diff --git a/docs/apidocs.swagger.json b/docs/apidocs.swagger.json index 95ef58480..44b9eb84b 100644 --- a/docs/apidocs.swagger.json +++ b/docs/apidocs.swagger.json @@ -47,6 +47,7 @@ "/v1/tenants/create": { "post": { "summary": "create new tenant", + "description": "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.\n \u003cWarning\u003eWe have a pre-inserted tenant - t1 - by default for the ones that don't use multi-tenancy.\u003c/Warning\u003e", "operationId": "tenants.create", "responses": { "200": { @@ -75,12 +76,25 @@ ], "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})" + } ] } }, "/v1/tenants/list": { "post": { "summary": "list tenants", + "description": "You can list tenants with following API.", "operationId": "tenants.list", "responses": { "200": { @@ -109,12 +123,25 @@ ], "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})" + } ] } }, "/v1/tenants/{id}": { "delete": { "summary": "delete tenant", + "description": "You can delete a tenant with following API.", "operationId": "tenants.delete", "responses": { "200": { @@ -141,12 +168,25 @@ ], "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})" + } ] } }, "/v1/tenants/{tenant_id}/bundle/delete": { "post": { "summary": "delete bundle", + "description": "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.", "operationId": "bundle.delete", "responses": { "200": { @@ -187,12 +227,25 @@ ], "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})" + } ] } }, "/v1/tenants/{tenant_id}/bundle/read": { "post": { "summary": "read bundle", + "description": "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.", "operationId": "bundle.read", "responses": { "200": { @@ -231,12 +284,25 @@ ], "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})" + } ] } }, "/v1/tenants/{tenant_id}/bundle/write": { "post": { "summary": "write bundle", + "description": "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.", "operationId": "bundle.write", "responses": { "200": { @@ -281,12 +347,25 @@ ], "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})" + } ] } }, "/v1/tenants/{tenant_id}/data/attributes/read": { "post": { "summary": "read attribute(s)", + "description": "Read API allows for directly querying the stored graph data to display and filter stored attributes.", "operationId": "data.attributes.read", "responses": { "200": { @@ -341,12 +420,25 @@ ], "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})" + } ] } }, "/v1/tenants/{tenant_id}/data/delete": { "post": { "summary": "delete data", + "description": "You can delete any stored relation tuples or attributes with following API.", "operationId": "data.delete", "responses": { "200": { @@ -392,12 +484,25 @@ ], "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})" + } ] } }, "/v1/tenants/{tenant_id}/data/relationships/read": { "post": { "summary": "read relation tuple(s)", + "description": "Read API allows for directly querying the stored graph data to display and filter stored relational tuples.", "operationId": "data.relationships.read", "responses": { "200": { @@ -452,12 +557,25 @@ ], "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})" + } ] } }, "/v1/tenants/{tenant_id}/data/run-bundle": { "post": { "summary": "run bundle", + "description": "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.", "operationId": "bundle.run", "responses": { "200": { @@ -505,12 +623,25 @@ ], "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})" + } ] } }, "/v1/tenants/{tenant_id}/data/write": { "post": { "summary": "create data", + "description": "In Permify, attributes and relations between your entities, objects and users represents your authorization data. These data stored as tuples in a preferred database.", "operationId": "data.write", "responses": { "200": { @@ -568,12 +699,25 @@ ], "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})" + } ] } }, "/v1/tenants/{tenant_id}/permissions/check": { "post": { "summary": "This method returns a decision about whether user can perform an permission on a certain resource.", + "description": "In Permify, you can perform two different types access checks,\n\n resource based authorization checks, in form of Can user U perform action Y in resource Z?\nsubject based authorization checks,\n\n in form of Which resources can user U edit?\nIn this section we'll look at the resource based check request of Permify.\nYou can find subject based access checks in Entity (Data) Filtering section.", "operationId": "permissions.check", "responses": { "200": { @@ -639,12 +783,25 @@ ], "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})" + } ] } }, "/v1/tenants/{tenant_id}/permissions/expand": { "post": { "summary": "expand relationships according to schema", + "description": "Retrieve all subjects (users and user sets) that have a relationship or attribute with given entity and permission.\nExpand API response is represented by a user set tree, whose leaf nodes are user IDs or user sets pointing to other ⟨object#relation⟩ pairs.\n \u003cTip\u003eWHEN TO USE ?\nExpand 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.\n 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.\u003c/Tip\u003e", "operationId": "permissions.expand", "responses": { "200": { @@ -706,12 +863,25 @@ ], "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})" + } ] } }, "/v1/tenants/{tenant_id}/permissions/lookup-entity": { "post": { "summary": "Retrieve an entity by its identifier.", + "description": "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.", "operationId": "permissions.lookupEntity", "responses": { "200": { @@ -769,12 +939,25 @@ ], "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})" + } ] } }, "/v1/tenants/{tenant_id}/permissions/lookup-entity-stream": { "post": { "summary": "Stream entities by their identifiers.", + "description": "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 as a streaming response.", "operationId": "permissions.lookupEntityStream", "responses": { "200": { @@ -841,12 +1024,25 @@ ], "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.", + "description": "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.", "operationId": "permissions.lookupSubject", "responses": { "200": { @@ -904,12 +1100,25 @@ ], "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})" + } ] } }, "/v1/tenants/{tenant_id}/permissions/subject-permission": { "post": { "summary": "Retrieve permissions related to a specific subject.", + "description": "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. \n\n In this endpoint, you'll receive a map of permissions and their statuses directly. The structure is map[string]CheckResult, such as \"sample-permission\" -\u003e \"ALLOWED\". This represents the permissions and their associated states in a key-value pair format.", "operationId": "permissions.subjectPermission", "responses": { "200": { @@ -963,6 +1172,18 @@ ], "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})" + } ] } }, @@ -1069,6 +1290,7 @@ "/v1/tenants/{tenant_id}/schemas/list": { "post": { "summary": "list all authorization models", + "description": "Models written to Permify using the write schema API can be listed using this API with the timestamps at which the models were created.", "operationId": "schemas.list", "responses": { "200": { @@ -1115,12 +1337,25 @@ ], "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})" + } ] } }, "/v1/tenants/{tenant_id}/schemas/read": { "post": { "summary": "read your authorization model", + "description": "When a model is written to Permify using the write schema API a schema version will be returned by the API. That schema version can be used to inspect the schema.", "operationId": "schemas.read", "responses": { "200": { @@ -1162,12 +1397,25 @@ ], "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 })" + } ] } }, "/v1/tenants/{tenant_id}/schemas/write": { "post": { "summary": "write your authorization model", + "description": "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 as well as in any IDE or text editor.", "operationId": "schemas.write", "responses": { "200": { @@ -1209,11 +1457,24 @@ ], "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})" + } ] } }, "/v1/tenants/{tenant_id}/watch": { "post": { + "description": "The Permify Watch API acts as a real-time broadcaster that shows changes in the relation tuples.", "operationId": "watch.watch", "responses": { "200": { @@ -1264,6 +1525,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" + } ] } } diff --git a/pkg/pb/base/v1/service.pb.go b/pkg/pb/base/v1/service.pb.go index 7046607af..96b668bdf 100644 --- a/pkg/pb/base/v1/service.pb.go +++ b/pkg/pb/base/v1/service.pb.go @@ -4171,279 +4171,1661 @@ var file_base_v1_service_proto_rawDesc = []byte{ 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, + 0x65, 0x6e, 0x32, 0x89, 0x4b, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x9e, 0x0e, 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, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xd1, + 0x0d, 0x92, 0x41, 0x99, 0x0d, 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, - 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, - 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, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x1a, 0x85, 0x03, 0x49, 0x6e, 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x66, 0x79, 0x2c, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x70, 0x65, 0x72, 0x66, + 0x6f, 0x72, 0x6d, 0x20, 0x74, 0x77, 0x6f, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x73, 0x20, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x20, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x73, 0x2c, 0x0a, 0x0a, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x20, 0x62, 0x61, 0x73, 0x65, 0x64, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x2c, 0x20, 0x69, 0x6e, + 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6f, 0x66, 0x20, 0x43, 0x61, 0x6e, 0x20, 0x75, 0x73, 0x65, + 0x72, 0x20, 0x55, 0x20, 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x59, 0x20, 0x69, 0x6e, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x20, 0x5a, 0x3f, 0x0a, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x62, 0x61, 0x73, 0x65, + 0x64, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x2c, 0x0a, 0x0a, 0x20, 0x69, 0x6e, 0x20, 0x66, 0x6f, 0x72, + 0x6d, 0x20, 0x6f, 0x66, 0x20, 0x57, 0x68, 0x69, 0x63, 0x68, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x55, 0x20, + 0x65, 0x64, 0x69, 0x74, 0x3f, 0x0a, 0x49, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x65, 0x27, 0x6c, 0x6c, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, + 0x20, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x20, 0x62, 0x61, 0x73, 0x65, 0x64, 0x20, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x20, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2e, + 0x0a, 0x59, 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x66, 0x69, 0x6e, 0x64, 0x20, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x62, 0x61, 0x73, 0x65, 0x64, 0x20, 0x61, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x20, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x20, 0x28, 0x44, 0x61, 0x74, 0x61, 0x29, 0x20, 0x46, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x20, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x2a, 0x11, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x6a, 0x8b, 0x09, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, + 0x65, 0x73, 0x12, 0xf9, 0x08, 0x32, 0xf6, 0x08, 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, 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, 0xd3, 0x0b, 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, 0x83, 0x0b, 0x92, 0x41, 0xca, 0x0a, 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, 0x1a, + 0xe8, 0x04, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x73, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x20, 0x28, 0x75, 0x73, 0x65, 0x72, 0x73, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x73, 0x65, 0x74, 0x73, 0x29, 0x20, 0x74, 0x68, + 0x61, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x61, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x20, 0x6f, 0x72, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x67, 0x69, 0x76, 0x65, 0x6e, 0x20, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x20, 0x41, 0x50, 0x49, + 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x70, + 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x61, 0x20, 0x75, 0x73, + 0x65, 0x72, 0x20, 0x73, 0x65, 0x74, 0x20, 0x74, 0x72, 0x65, 0x65, 0x2c, 0x20, 0x77, 0x68, 0x6f, + 0x73, 0x65, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x20, 0x61, 0x72, + 0x65, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x49, 0x44, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x75, 0x73, + 0x65, 0x72, 0x20, 0x73, 0x65, 0x74, 0x73, 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, + 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0xe2, 0x9f, 0xa8, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x23, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0xe2, 0x9f, 0xa9, 0x20, + 0x70, 0x61, 0x69, 0x72, 0x73, 0x2e, 0x0a, 0x20, 0x3c, 0x54, 0x69, 0x70, 0x3e, 0x57, 0x48, 0x45, + 0x4e, 0x20, 0x54, 0x4f, 0x20, 0x55, 0x53, 0x45, 0x20, 0x3f, 0x0a, 0x45, 0x78, 0x70, 0x61, 0x6e, + 0x64, 0x20, 0x69, 0x73, 0x20, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x73, 0x65, 0x74, 0x20, 0x6f, 0x66, 0x20, + 0x75, 0x73, 0x65, 0x72, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, + 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x69, 0x72, 0x20, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x2c, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x61, + 0x6c, 0x6c, 0x6f, 0x77, 0x73, 0x20, 0x6f, 0x75, 0x72, 0x20, 0x75, 0x73, 0x65, 0x72, 0x73, 0x20, + 0x74, 0x6f, 0x20, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x20, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, + 0x6e, 0x74, 0x20, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x65, + 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x2d, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2e, + 0x0a, 0x20, 0x49, 0x74, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x64, 0x65, 0x73, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x73, 0x20, 0x61, + 0x20, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x20, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x2e, 0x20, 0x45, + 0x78, 0x70, 0x61, 0x6e, 0x64, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x68, 0x61, + 0x73, 0x20, 0x61, 0x20, 0x68, 0x69, 0x67, 0x68, 0x20, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, + 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x63, 0x61, 0x75, 0x73, 0x65, + 0x20, 0x61, 0x20, 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x69, + 0x73, 0x73, 0x75, 0x65, 0x73, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x69, 0x74, 0x73, 0x20, 0x75, + 0x73, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x20, 0x63, 0x68, + 0x65, 0x63, 0x6b, 0x2e, 0x3c, 0x2f, 0x54, 0x69, 0x70, 0x3e, 0x2a, 0x12, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x6a, 0x92, + 0x05, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, + 0x12, 0x80, 0x05, 0x32, 0xfd, 0x04, 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, 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, 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, 0xf0, 0x09, 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, 0x2d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, + 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, 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, + 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8e, 0x09, 0x92, 0x41, 0xce, + 0x08, 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, 0x1a, 0xb8, 0x01, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x6c, 0x65, + 0x74, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x73, 0x6b, 0x20, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6f, 0x66, 0x20, + 0xe2, 0x80, 0x9c, 0x57, 0x68, 0x69, 0x63, 0x68, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x75, 0x73, 0x65, 0x72, 0x3a, 0x58, 0x20, 0x64, 0x6f, + 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x59, 0x3f, 0xe2, 0x80, 0x9d, 0x2e, 0x20, 0x41, + 0x73, 0x20, 0x61, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x79, 0x6f, 0x75, 0xe2, 0x80, 0x99, 0x6c, 0x6c, 0x20, 0x67, 0x65, + 0x74, 0x20, 0x61, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x6f, + 0x66, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 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, 0x6a, 0xc3, 0x06, 0x0a, 0x0d, 0x78, 0x2d, + 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xb1, 0x06, 0x32, 0xae, + 0x06, 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, 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, 0xc8, 0x0e, 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, 0xd8, 0x0d, 0x92, 0x41, 0x91, 0x0d, + 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, 0x1a, 0xc3, 0x01, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x6c, 0x65, 0x74, + 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x73, 0x6b, 0x20, 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6f, 0x66, 0x20, 0xe2, + 0x80, 0x9c, 0x57, 0x68, 0x69, 0x63, 0x68, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x75, 0x73, 0x65, 0x72, 0x3a, 0x58, 0x20, 0x64, 0x6f, 0x20, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x59, 0x3f, 0xe2, 0x80, 0x9d, 0x2e, 0x20, 0x41, 0x73, + 0x20, 0x61, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x69, 0x73, 0x20, 0x79, 0x6f, 0x75, 0xe2, 0x80, 0x99, 0x6c, 0x6c, 0x20, 0x67, 0x65, 0x74, + 0x20, 0x61, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x6f, 0x66, + 0x20, 0x61, 0x73, 0x20, 0x61, 0x20, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x20, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 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, - 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, - 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, - 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, - 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, - 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, + 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, 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, 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, - 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, - 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, - 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, + 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, 0xf5, 0x0a, 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, 0x90, 0x0a, 0x92, 0x41, + 0xcf, 0x09, 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, 0x1a, 0xbe, 0x01, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20, 0x53, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, + 0x6c, 0x65, 0x74, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x73, 0x6b, 0x20, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6f, + 0x66, 0x20, 0xe2, 0x80, 0x9c, 0x57, 0x68, 0x69, 0x63, 0x68, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x64, 0x6f, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x59, 0x20, 0x6f, 0x6e, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x58, 0x3f, + 0xe2, 0x80, 0x9d, 0x2e, 0x20, 0x41, 0x73, 0x20, 0x61, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x79, 0x6f, 0x75, 0xe2, 0x80, + 0x99, 0x6c, 0x6c, 0x20, 0x67, 0x65, 0x74, 0x20, 0x61, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x66, + 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, + 0x61, 0x72, 0x72, 0x61, 0x79, 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, 0x6a, 0xbd, 0x07, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x73, 0x12, 0xab, 0x07, 0x32, 0xa8, 0x07, 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, 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, 0xcd, + 0x0d, 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, + 0xdc, 0x0c, 0x92, 0x41, 0x97, 0x0c, 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, 0x1a, 0x81, 0x04, 0x54, 0x68, 0x65, 0x20, 0x53, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x20, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x61, + 0x6c, 0x6c, 0x6f, 0x77, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x74, 0x6f, 0x20, 0x69, 0x6e, 0x71, + 0x75, 0x69, 0x72, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, + 0x20, 0x6f, 0x66, 0x20, 0xe2, 0x80, 0x9c, 0x57, 0x68, 0x69, 0x63, 0x68, 0x20, 0x70, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x75, 0x73, 0x65, 0x72, 0x3a, 0x78, 0x20, + 0x63, 0x61, 0x6e, 0x20, 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6f, 0x6e, 0x20, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x79, 0x3f, 0xe2, 0x80, 0x9d, 0x2e, 0x20, 0x49, 0x6e, 0x20, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2c, 0x20, 0x79, 0x6f, 0x75, 0x27, 0x6c, 0x6c, + 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, + 0x6f, 0x66, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x73, + 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x75, + 0x73, 0x65, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, 0x69, 0x76, 0x65, + 0x6e, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2c, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, + 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, + 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x6d, 0x61, 0x70, 0x2e, 0x20, 0x0a, 0x0a, 0x20, 0x49, 0x6e, + 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2c, 0x20, + 0x79, 0x6f, 0x75, 0x27, 0x6c, 0x6c, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, 0x61, + 0x20, 0x6d, 0x61, 0x70, 0x20, 0x6f, 0x66, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x69, 0x72, 0x20, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6c, 0x79, 0x2e, + 0x20, 0x54, 0x68, 0x65, 0x20, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x20, 0x69, + 0x73, 0x20, 0x6d, 0x61, 0x70, 0x5b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5d, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2c, 0x20, 0x73, 0x75, 0x63, 0x68, 0x20, 0x61, + 0x73, 0x20, 0x22, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x20, 0x2d, 0x3e, 0x20, 0x22, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x45, + 0x44, 0x22, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, + 0x6e, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x69, 0x72, 0x20, 0x61, 0x73, + 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x64, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, 0x20, + 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6b, 0x65, 0x79, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x70, + 0x61, 0x69, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 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, 0x6a, 0xb0, 0x07, 0x0a, 0x0d, 0x78, + 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0x9e, 0x07, 0x32, + 0x9b, 0x07, 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, 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, 0xd6, + 0x08, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0xcc, 0x08, 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, 0x91, 0x08, 0x92, 0x41, 0xe5, 0x07, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x1a, + 0x60, 0x54, 0x68, 0x65, 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x20, 0x57, 0x61, 0x74, + 0x63, 0x68, 0x20, 0x41, 0x50, 0x49, 0x20, 0x61, 0x63, 0x74, 0x73, 0x20, 0x61, 0x73, 0x20, 0x61, + 0x20, 0x72, 0x65, 0x61, 0x6c, 0x2d, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x62, 0x72, 0x6f, 0x61, 0x64, + 0x63, 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x73, 0x68, 0x6f, 0x77, + 0x73, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, + 0x2e, 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, 0xca, 0x19, 0x0a, 0x06, 0x53, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x12, 0xb7, 0x0d, 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, 0xf2, 0x0c, 0x92, 0x41, 0xbe, 0x0c, 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, 0x1a, 0xd2, 0x01, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, + 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x20, 0x69, 0x74, 0x27, 0x73, 0x20, 0x6f, 0x77, + 0x6e, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x6f, 0x64, 0x65, + 0x6c, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, + 0x73, 0x20, 0x6f, 0x66, 0x20, 0x65, 0x61, 0x73, 0x69, 0x6c, 0x79, 0x2e, 0x20, 0x57, 0x65, 0x20, + 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x20, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x20, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x20, 0x61, 0x6e, + 0x64, 0x20, 0x69, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x6f, 0x75, 0x72, 0x20, 0x70, 0x6c, 0x61, 0x79, 0x67, + 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x61, 0x73, 0x20, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x61, 0x73, + 0x20, 0x69, 0x6e, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x49, 0x44, 0x45, 0x20, 0x6f, 0x72, 0x20, 0x74, + 0x65, 0x78, 0x74, 0x20, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x2e, 0x2a, 0x0d, 0x73, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x73, 0x2e, 0x77, 0x72, 0x69, 0x74, 0x65, 0x6a, 0xaf, 0x0a, 0x0a, 0x0d, 0x78, + 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0x9d, 0x0a, 0x32, + 0x9a, 0x0a, 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, 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, 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, - 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, + 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, 0xeb, 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, 0xa9, + 0x06, 0x92, 0x41, 0xf6, 0x05, 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, 0x1a, 0xa2, 0x01, 0x57, + 0x68, 0x65, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x20, 0x69, 0x73, 0x20, 0x77, + 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x66, + 0x79, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, 0x72, 0x69, 0x74, + 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x20, 0x41, 0x50, 0x49, 0x20, 0x61, 0x20, 0x73, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x69, + 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x20, 0x62, + 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x41, 0x50, 0x49, 0x2e, 0x20, 0x54, 0x68, 0x61, 0x74, 0x20, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x63, + 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x69, 0x6e, + 0x73, 0x70, 0x65, 0x63, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x2e, 0x2a, 0x0c, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2e, 0x72, 0x65, 0x61, 0x64, 0x6a, + 0x99, 0x04, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x73, 0x12, 0x87, 0x04, 0x32, 0x84, 0x04, 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, 0x88, 0x02, 0x2a, 0x85, 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, 0xdb, 0x01, + 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xd0, 0x01, 0x1a, 0xcd, 0x01, 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, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 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, 0x20, 0x20, 0x20, 0x20, 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, 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, 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, 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, 0x29, 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, 0x97, 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, 0xd5, 0x04, 0x92, + 0x41, 0xa2, 0x04, 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, 0x1a, 0x87, 0x01, 0x4d, 0x6f, 0x64, + 0x65, 0x6c, 0x73, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x20, 0x41, 0x50, + 0x49, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x20, + 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x41, 0x50, 0x49, 0x20, 0x77, + 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x73, 0x20, 0x61, 0x74, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x20, 0x77, 0x65, 0x72, 0x65, 0x20, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x2e, 0x2a, 0x0c, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2e, 0x6c, 0x69, + 0x73, 0x74, 0x6a, 0xe0, 0x02, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x73, 0x12, 0xce, 0x02, 0x32, 0xcb, 0x02, 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, 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, 0xcd, 0x36, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x93, 0x10, + 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, 0xd2, + 0x0f, 0x92, 0x41, 0xa1, 0x0f, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0b, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x20, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xa6, 0x01, 0x49, 0x6e, 0x20, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2c, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, + 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x75, 0x73, 0x65, 0x72, 0x73, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, + 0x6e, 0x74, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x20, 0x54, 0x68, 0x65, + 0x73, 0x65, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x61, + 0x73, 0x20, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x70, 0x72, + 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x20, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, + 0x2e, 0x2a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x77, 0x72, 0x69, 0x74, 0x65, 0x6a, 0xd6, 0x0d, + 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, + 0xc4, 0x0d, 0x32, 0xc1, 0x0d, 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, 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, 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, 0xe8, 0x09, 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, 0x8d, 0x09, 0x92, + 0x41, 0xcf, 0x08, 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, 0x1a, 0x6b, 0x52, 0x65, 0x61, 0x64, 0x20, 0x41, 0x50, 0x49, 0x20, 0x61, 0x6c, 0x6c, 0x6f, + 0x77, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6c, 0x79, 0x20, + 0x71, 0x75, 0x65, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x64, 0x20, 0x67, 0x72, 0x61, 0x70, 0x68, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x74, + 0x6f, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x66, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x72, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x2e, 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, 0xa8, 0x07, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, + 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0x96, 0x07, 0x32, 0x93, 0x07, 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, 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, 0xb5, 0x08, 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, 0xe3, + 0x07, 0x92, 0x41, 0xa8, 0x07, 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, 0x1a, 0x64, + 0x52, 0x65, 0x61, 0x64, 0x20, 0x41, 0x50, 0x49, 0x20, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x73, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6c, 0x79, 0x20, 0x71, 0x75, 0x65, + 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, + 0x20, 0x67, 0x72, 0x61, 0x70, 0x68, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x74, 0x6f, 0x20, 0x64, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x66, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x73, 0x2e, 0x2a, 0x14, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x61, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x61, 0x64, 0x6a, 0x90, 0x06, 0x0a, 0x0d, 0x78, + 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xfe, 0x05, 0x32, + 0xfb, 0x05, 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, 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, 0x86, 0x09, 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, 0xc2, 0x08, 0x92, 0x41, 0x90, 0x08, 0x0a, + 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0b, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x64, 0x61, + 0x74, 0x61, 0x1a, 0x4b, 0x59, 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x64, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x20, 0x6f, 0x72, + 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, + 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x20, 0x41, 0x50, 0x49, 0x2e, 0x2a, + 0x0b, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x6a, 0xa0, 0x07, 0x0a, + 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0x8e, + 0x07, 0x32, 0x8b, 0x07, 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, 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, 0xe5, 0x07, 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, 0xa0, 0x07, 0x92, 0x41, 0xea, 0x06, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0a, + 0x72, 0x75, 0x6e, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x1a, 0xfc, 0x01, 0x54, 0x68, 0x65, + 0x20, 0x22, 0x52, 0x75, 0x6e, 0x20, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0x20, 0x41, 0x50, + 0x49, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x74, 0x72, + 0x61, 0x69, 0x67, 0x68, 0x74, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x20, 0x77, 0x61, 0x79, + 0x20, 0x74, 0x6f, 0x20, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x20, 0x70, 0x72, 0x65, 0x64, + 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x20, 0x77, + 0x69, 0x74, 0x68, 0x69, 0x6e, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x27, 0x73, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, + 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x20, 0x42, 0x79, 0x20, + 0x73, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x65, + 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2c, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, + 0x20, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, + 0x69, 0x63, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x69, + 0x65, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x20, + 0x65, 0x6e, 0x63, 0x61, 0x70, 0x73, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, + 0x61, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x2a, 0x0a, 0x62, 0x75, 0x6e, 0x64, 0x6c, + 0x65, 0x2e, 0x72, 0x75, 0x6e, 0x6a, 0xca, 0x04, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, + 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xb8, 0x04, 0x32, 0xb5, 0x04, 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, 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, 0x90, 0x1d, 0x0a, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, + 0xbc, 0x10, 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, 0xf7, 0x0f, 0x92, 0x41, 0xc4, 0x0f, 0x0a, 0x06, 0x42, 0x75, 0x6e, + 0x64, 0x6c, 0x65, 0x12, 0x0c, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, + 0x65, 0x1a, 0x91, 0x02, 0x54, 0x68, 0x65, 0x20, 0x22, 0x57, 0x72, 0x69, 0x74, 0x65, 0x20, 0x42, + 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0x20, 0x41, 0x50, 0x49, 0x20, 0x69, 0x73, 0x20, 0x64, 0x65, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x6c, + 0x69, 0x6e, 0x67, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x75, + 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x20, 0x49, 0x74, 0x73, 0x20, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, + 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x77, + 0x72, 0x69, 0x74, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, + 0x64, 0x61, 0x74, 0x61, 0x20, 0x61, 0x63, 0x63, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x74, + 0x6f, 0x20, 0x70, 0x72, 0x65, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x73, 0x74, 0x72, + 0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x41, 0x50, + 0x49, 0x20, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x73, 0x20, 0x75, 0x73, 0x65, 0x72, 0x73, 0x20, 0x74, + 0x6f, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x6f, 0x72, 0x20, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x2c, + 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x64, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x75, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x61, 0x20, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, + 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x2a, 0x0c, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x77, 0x72, + 0x69, 0x74, 0x65, 0x6a, 0x89, 0x0d, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xf7, 0x0c, 0x32, 0xf4, 0x0c, 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, 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, 0xa1, + 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, 0xdf, 0x05, 0x92, 0x41, 0xad, 0x05, 0x0a, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, + 0x0b, 0x72, 0x65, 0x61, 0x64, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x1a, 0xfd, 0x01, 0x54, + 0x68, 0x65, 0x20, 0x22, 0x52, 0x65, 0x61, 0x64, 0x20, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, + 0x20, 0x41, 0x50, 0x49, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x63, 0x72, 0x75, 0x63, 0x69, 0x61, + 0x6c, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, 0x74, 0x72, 0x69, + 0x65, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x20, 0x6f, 0x66, + 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x62, + 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x75, 0x6c, 0x74, + 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x65, 0x74, 0x75, 0x70, 0x2e, 0x20, 0x49, 0x74, 0x20, 0x69, + 0x73, 0x20, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2c, + 0x20, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x6c, 0x79, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x69, 0x74, 0x73, 0x20, 0x6e, 0x61, 0x6d, 0x65, + 0x2c, 0x20, 0x77, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x70, 0x65, + 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x27, 0x73, 0x20, + 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x2a, 0x0b, 0x62, 0x75, + 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x72, 0x65, 0x61, 0x64, 0x6a, 0x88, 0x03, 0x0a, 0x0d, 0x78, 0x2d, + 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xf6, 0x02, 0x32, 0xf3, + 0x02, 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, 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, 0xa2, 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, 0xda, 0x05, 0x92, 0x41, 0xa6, + 0x05, 0x0a, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x0d, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x1a, 0xeb, 0x01, 0x54, 0x68, 0x65, 0x20, 0x22, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0x20, 0x41, + 0x50, 0x49, 0x20, 0x69, 0x73, 0x20, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x73, 0x70, 0x65, 0x63, + 0x69, 0x66, 0x69, 0x63, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, + 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, + 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x2e, + 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x41, 0x50, 0x49, 0x20, 0x66, 0x61, 0x63, 0x69, 0x6c, 0x69, + 0x74, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2c, 0x20, + 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x69, 0x74, + 0x73, 0x20, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x20, 0x66, + 0x72, 0x6f, 0x6d, 0x20, 0x61, 0x20, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x65, 0x64, + 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x27, 0x73, 0x20, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, + 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x2a, 0x0d, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x64, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x6a, 0x8f, 0x03, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, + 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xfd, 0x02, 0x32, 0xfa, 0x02, 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, 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, 0xe8, 0x0d, 0x0a, 0x07, 0x54, 0x65, 0x6e, 0x61, + 0x6e, 0x63, 0x79, 0x12, 0xec, 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, 0xa4, 0x05, 0x92, 0x41, + 0x83, 0x05, 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, 0x1a, 0x85, + 0x02, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x20, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x20, 0x54, + 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x79, + 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x63, 0x75, + 0x73, 0x74, 0x6f, 0x6d, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x20, 0x66, 0x6f, 0x72, + 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x20, 0x74, 0x68, 0x65, 0x6d, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x73, 0x69, + 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x2e, 0x20, 0x59, 0x6f, 0x75, 0x20, + 0x63, 0x61, 0x6e, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, + 0x6e, 0x67, 0x20, 0x41, 0x50, 0x49, 0x2e, 0x0a, 0x20, 0x3c, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, + 0x67, 0x3e, 0x57, 0x65, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x61, 0x20, 0x70, 0x72, 0x65, 0x2d, + 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, + 0x2d, 0x20, 0x74, 0x31, 0x20, 0x2d, 0x20, 0x62, 0x79, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x6e, 0x65, 0x73, 0x20, 0x74, + 0x68, 0x61, 0x74, 0x20, 0x64, 0x6f, 0x6e, 0x27, 0x74, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6d, 0x75, + 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x2e, 0x3c, 0x2f, 0x57, 0x61, + 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x3e, 0x2a, 0x0e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2e, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x6a, 0xcc, 0x02, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, + 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xba, 0x02, 0x32, 0xb7, 0x02, 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, 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, 0xef, 0x03, 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, 0xa7, 0x03, 0x92, 0x41, 0x8b, + 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, 0x1a, 0x2b, 0x59, 0x6f, 0x75, 0x20, 0x63, + 0x61, 0x6e, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x61, 0x20, 0x74, 0x65, 0x6e, 0x61, + 0x6e, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, + 0x67, 0x20, 0x41, 0x50, 0x49, 0x2e, 0x2a, 0x0e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2e, + 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x6a, 0xb3, 0x02, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, + 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xa1, 0x02, 0x32, 0x9e, 0x02, 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, 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, 0xfb, 0x03, 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, 0xb9, 0x03, 0x92, 0x41, 0x9a, 0x03, 0x0a, 0x07, 0x54, + 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x12, 0x0c, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x74, 0x65, 0x6e, + 0x61, 0x6e, 0x74, 0x73, 0x1a, 0x28, 0x59, 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x6c, 0x69, + 0x73, 0x74, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, + 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x20, 0x41, 0x50, 0x49, 0x2e, 0x2a, 0x0c, + 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2e, 0x6c, 0x69, 0x73, 0x74, 0x6a, 0xc8, 0x02, 0x0a, + 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xb6, + 0x02, 0x32, 0xb3, 0x02, 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, - 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, + 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, 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, diff --git a/proto/base/v1/service.proto b/proto/base/v1/service.proto index 7abe541d0..7ff3cc140 100644 --- a/proto/base/v1/service.proto +++ b/proto/base/v1/service.proto @@ -29,6 +29,46 @@ service Permission { "Permission" ] operation_id: "permissions.check" + description: "In Permify, you can perform two different types access checks,\n\n resource based authorization checks, in form of Can user U perform action Y in resource Z?\nsubject based authorization checks,\n\n in form of Which resources can user U edit?\nIn this section we'll look at the resource based check request of Permify.\nYou can find subject based access checks in Entity (Data) Filtering section.", + 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})" }, + } + } + } + } + } + } }; } @@ -47,6 +87,46 @@ service Permission { "Permission" ] operation_id: "permissions.expand" + description: "Retrieve all subjects (users and user sets) that have a relationship or attribute with given entity and permission.\nExpand API response is represented by a user set tree, whose leaf nodes are user IDs or user sets pointing to other ⟨object#relation⟩ pairs.\n WHEN TO USE ?\nExpand 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.\n 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.", + 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})"}, + } + } + } + } + } + } }; } @@ -65,6 +145,46 @@ service Permission { "Permission" ] operation_id: "permissions.lookupEntity" + description: "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." + 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})"}, + } + } + } + } + } + } }; } @@ -83,6 +203,46 @@ service Permission { "Permission" ] operation_id: "permissions.lookupEntityStream" + description: "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 as a streaming response." + 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}"}, + } + } + } + } + } + } }; } @@ -101,6 +261,46 @@ service Permission { "Permission" ] operation_id: "permissions.lookupSubject" + description: "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." + 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})"}, + } + } + } + } + } + } }; } @@ -119,6 +319,46 @@ service Permission { "Permission" ] operation_id: "permissions.subjectPermission" + description: "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. \n\n 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." + 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})"}, + } + } + } + } + } + } }; } } @@ -458,6 +698,46 @@ service Watch { "Watch" // Adds an additional categorization for the operation. ] operation_id: "watch.watch" // Unique string used to identify the operation. + description: "The Permify Watch API acts as a real-time broadcaster that shows changes in the relation tuples." + 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"}, + } + } + } + } + } + } }; } } @@ -508,6 +788,46 @@ service Schema { summary: "write your authorization model" // 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: "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 as well as in any IDE or text editor." + 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})"}, + } + } + } + } + } + } }; } @@ -526,6 +846,46 @@ service Schema { summary: "read your authorization model" // 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: "When a model is written to Permify using the write schema API a schema version will be returned by the API. That schema version can be used to inspect the schema." + 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 })"}, + } + } + } + } + } + } }; } @@ -544,6 +904,46 @@ service Schema { summary: "list all authorization models" // 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: "Models written to Permify using the write schema API can be listed using this API with the timestamps at which the models were created." + 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})"}, + } + } + } + } + } + } }; } } @@ -608,7 +1008,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. @@ -670,6 +1070,46 @@ service Data { "Data" ] operation_id: "data.write" + description: "In Permify, attributes and relations between your entities, objects and users represents your authorization data. These data stored as tuples in a preferred database." + 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})"}, + } + } + } + } + } + } }; } @@ -702,6 +1142,46 @@ service Data { "Data" ] operation_id: "data.relationships.read" + description: "Read API allows for directly querying the stored graph data to display and filter stored relational tuples." + 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})"}, + } + } + } + } + } + } }; } @@ -718,6 +1198,46 @@ service Data { "Data" ] operation_id: "data.attributes.read" + description: "Read API allows for directly querying the stored graph data to display and filter stored attributes." + 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})"}, + } + } + } + } + } + } }; } @@ -734,6 +1254,46 @@ service Data { "Data" ] operation_id: "data.delete" + description: "You can delete any stored relation tuples or attributes with following API." + 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})"}, + } + } + } + } + } + } }; } @@ -766,6 +1326,46 @@ service Data { "Data" ] operation_id: "bundle.run" + description: "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." + 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})"}, + } + } + } + } + } + } }; } } @@ -1022,6 +1622,46 @@ service Bundle { "Bundle" ] operation_id: "bundle.write" + description: "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." + 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})"}, + } + } + } + } + } + } }; } @@ -1038,6 +1678,46 @@ service Bundle { "Bundle" ] operation_id: "bundle.read" + description: "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." + 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})"}, + } + } + } + } + } + } }; } @@ -1054,6 +1734,46 @@ service Bundle { "Bundle" ] operation_id: "bundle.delete" + description: "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." + 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})"}, + } + } + } + } + } + } }; } } @@ -1124,6 +1844,46 @@ service Tenancy { "Tenancy" ] operation_id: "tenants.create" + description: "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.\n We have a pre-inserted tenant - t1 - by default for the ones that don't use multi-tenancy.", + 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})"}, + } + } + } + } + } + } }; } @@ -1140,6 +1900,46 @@ service Tenancy { "Tenancy" ] operation_id: "tenants.delete" + description: "You can delete a tenant with following API.", + 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})"}, + } + } + } + } + } + } }; } @@ -1157,6 +1957,46 @@ service Tenancy { "Tenancy" ] operation_id: "tenants.list" + description: "You can list tenants with following API.", + 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})"}, + } + } + } + } + } + } }; } } From 57e81037cc33522c48ee588cae4d723fef1797bd Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Fri, 15 Mar 2024 16:45:36 +0300 Subject: [PATCH 44/70] ci: remove publish docs action --- .github/workflows/docs.yaml | 58 ------------------------------------- 1 file changed, 58 deletions(-) delete mode 100644 .github/workflows/docs.yaml diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml deleted file mode 100644 index 056c145dc..000000000 --- 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 }} From 7d3b0177d635d898c5f13fe17c092853bcc11b72 Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Sat, 16 Mar 2024 16:58:29 +0300 Subject: [PATCH 45/70] feat(docs): curl code samples added --- docs/apidocs.swagger.json | 147 +- pkg/pb/base/v1/service.pb.go | 3372 ++++++++++++++++++---------------- proto/base/v1/service.proto | 378 +++- 3 files changed, 2224 insertions(+), 1673 deletions(-) diff --git a/docs/apidocs.swagger.json b/docs/apidocs.swagger.json index 44b9eb84b..2a90bb56a 100644 --- a/docs/apidocs.swagger.json +++ b/docs/apidocs.swagger.json @@ -46,8 +46,7 @@ "paths": { "/v1/tenants/create": { "post": { - "summary": "create new tenant", - "description": "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.\n \u003cWarning\u003eWe have a pre-inserted tenant - t1 - by default for the ones that don't use multi-tenancy.\u003c/Warning\u003e", + "summary": "create tenant", "operationId": "tenants.create", "responses": { "200": { @@ -87,6 +86,11 @@ "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}'" } ] } @@ -94,7 +98,6 @@ "/v1/tenants/list": { "post": { "summary": "list tenants", - "description": "You can list tenants with following API.", "operationId": "tenants.list", "responses": { "200": { @@ -134,6 +137,11 @@ "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,7 +149,6 @@ "/v1/tenants/{id}": { "delete": { "summary": "delete tenant", - "description": "You can delete a tenant with following API.", "operationId": "tenants.delete", "responses": { "200": { @@ -179,6 +186,11 @@ "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'" } ] } @@ -186,7 +198,6 @@ "/v1/tenants/{tenant_id}/bundle/delete": { "post": { "summary": "delete bundle", - "description": "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.", "operationId": "bundle.delete", "responses": { "200": { @@ -238,6 +249,11 @@ "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}'" } ] } @@ -245,7 +261,6 @@ "/v1/tenants/{tenant_id}/bundle/read": { "post": { "summary": "read bundle", - "description": "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.", "operationId": "bundle.read", "responses": { "200": { @@ -295,6 +310,11 @@ "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}'" } ] } @@ -302,7 +322,6 @@ "/v1/tenants/{tenant_id}/bundle/write": { "post": { "summary": "write bundle", - "description": "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.", "operationId": "bundle.write", "responses": { "200": { @@ -358,14 +377,18 @@ "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)", - "description": "Read API allows for directly querying the stored graph data to display and filter stored attributes.", + "summary": "read attributes", "operationId": "data.attributes.read", "responses": { "200": { @@ -431,6 +454,11 @@ "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}'" } ] } @@ -438,7 +466,6 @@ "/v1/tenants/{tenant_id}/data/delete": { "post": { "summary": "delete data", - "description": "You can delete any stored relation tuples or attributes with following API.", "operationId": "data.delete", "responses": { "200": { @@ -495,14 +522,18 @@ "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)", - "description": "Read API allows for directly querying the stored graph data to display and filter stored relational tuples.", + "summary": "read relationships", "operationId": "data.relationships.read", "responses": { "200": { @@ -568,6 +599,11 @@ "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}'" } ] } @@ -575,7 +611,6 @@ "/v1/tenants/{tenant_id}/data/run-bundle": { "post": { "summary": "run bundle", - "description": "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.", "operationId": "bundle.run", "responses": { "200": { @@ -634,14 +669,18 @@ "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", - "description": "In Permify, attributes and relations between your entities, objects and users represents your authorization data. These data stored as tuples in a preferred database.", + "summary": "write data", "operationId": "data.write", "responses": { "200": { @@ -710,14 +749,18 @@ "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.", - "description": "In Permify, you can perform two different types access checks,\n\n resource based authorization checks, in form of Can user U perform action Y in resource Z?\nsubject based authorization checks,\n\n in form of Which resources can user U edit?\nIn this section we'll look at the resource based check request of Permify.\nYou can find subject based access checks in Entity (Data) Filtering section.", + "summary": "check api", "operationId": "permissions.check", "responses": { "200": { @@ -794,14 +837,18 @@ "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", - "description": "Retrieve all subjects (users and user sets) that have a relationship or attribute with given entity and permission.\nExpand API response is represented by a user set tree, whose leaf nodes are user IDs or user sets pointing to other ⟨object#relation⟩ pairs.\n \u003cTip\u003eWHEN TO USE ?\nExpand 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.\n 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.\u003c/Tip\u003e", + "summary": "expand api", "operationId": "permissions.expand", "responses": { "200": { @@ -874,14 +921,18 @@ "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.", - "description": "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.", + "summary": "lookup entity", "operationId": "permissions.lookupEntity", "responses": { "200": { @@ -950,14 +1001,18 @@ "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.", - "description": "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 as a streaming response.", + "summary": "lookup entity stream", "operationId": "permissions.lookupEntityStream", "responses": { "200": { @@ -1041,8 +1096,7 @@ }, "/v1/tenants/{tenant_id}/permissions/lookup-subject": { "post": { - "summary": "Retrieve a subject by its identifier.", - "description": "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.", + "summary": "lookup-subject", "operationId": "permissions.lookupSubject", "responses": { "200": { @@ -1111,14 +1165,18 @@ "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.", - "description": "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. \n\n In this endpoint, you'll receive a map of permissions and their statuses directly. The structure is map[string]CheckResult, such as \"sample-permission\" -\u003e \"ALLOWED\". This represents the permissions and their associated states in a key-value pair format.", + "summary": "subject permission", "operationId": "permissions.subjectPermission", "responses": { "200": { @@ -1183,6 +1241,11 @@ "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}'" } ] } @@ -1234,7 +1297,7 @@ }, "/v1/tenants/{tenant_id}/relationships/write": { "post": { - "summary": "create new relationships", + "summary": "write relationships", "operationId": "relationships.write", "responses": { "200": { @@ -1289,8 +1352,7 @@ }, "/v1/tenants/{tenant_id}/schemas/list": { "post": { - "summary": "list all authorization models", - "description": "Models written to Permify using the write schema API can be listed using this API with the timestamps at which the models were created.", + "summary": "list schema", "operationId": "schemas.list", "responses": { "200": { @@ -1348,14 +1410,18 @@ "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", - "description": "When a model is written to Permify using the write schema API a schema version will be returned by the API. That schema version can be used to inspect the schema.", + "summary": "read schema", "operationId": "schemas.read", "responses": { "200": { @@ -1407,15 +1473,19 @@ { "label": "node", "lang": "javascript", - "source": "let res = client.schema.read({\n tenantId: \"t1\",\n metadata: {\n schemaVersion: swResponse.schemaVersion,\n },\n })" + "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", - "description": "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 as well as in any IDE or text editor.", + "summary": "write schema", "operationId": "schemas.write", "responses": { "200": { @@ -1468,13 +1538,18 @@ "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": { - "description": "The Permify Watch API acts as a real-time broadcaster that shows changes in the relation tuples.", + "summary": "watch changes", "operationId": "watch.watch", "responses": { "200": { diff --git a/pkg/pb/base/v1/service.pb.go b/pkg/pb/base/v1/service.pb.go index 96b668bdf..328cb8297 100644 --- a/pkg/pb/base/v1/service.pb.go +++ b/pkg/pb/base/v1/service.pb.go @@ -4171,1421 +4171,1479 @@ var file_base_v1_service_proto_rawDesc = []byte{ 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, 0x89, 0x4b, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x9e, 0x0e, 0x0a, 0x05, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1f, 0x2e, 0x62, 0x61, + 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, 0xd1, - 0x0d, 0x92, 0x41, 0x99, 0x0d, 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, 0x1a, 0x85, 0x03, 0x49, 0x6e, 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, - 0x66, 0x79, 0x2c, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x70, 0x65, 0x72, 0x66, - 0x6f, 0x72, 0x6d, 0x20, 0x74, 0x77, 0x6f, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, - 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x73, 0x20, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x20, 0x63, - 0x68, 0x65, 0x63, 0x6b, 0x73, 0x2c, 0x0a, 0x0a, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x20, 0x62, 0x61, 0x73, 0x65, 0x64, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x2c, 0x20, 0x69, 0x6e, - 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6f, 0x66, 0x20, 0x43, 0x61, 0x6e, 0x20, 0x75, 0x73, 0x65, - 0x72, 0x20, 0x55, 0x20, 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x20, 0x59, 0x20, 0x69, 0x6e, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x20, 0x5a, 0x3f, 0x0a, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x62, 0x61, 0x73, 0x65, - 0x64, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, - 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x2c, 0x0a, 0x0a, 0x20, 0x69, 0x6e, 0x20, 0x66, 0x6f, 0x72, - 0x6d, 0x20, 0x6f, 0x66, 0x20, 0x57, 0x68, 0x69, 0x63, 0x68, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x55, 0x20, - 0x65, 0x64, 0x69, 0x74, 0x3f, 0x0a, 0x49, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x65, 0x27, 0x6c, 0x6c, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, - 0x20, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x20, 0x62, 0x61, 0x73, 0x65, 0x64, 0x20, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x20, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2e, - 0x0a, 0x59, 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x66, 0x69, 0x6e, 0x64, 0x20, 0x73, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x62, 0x61, 0x73, 0x65, 0x64, 0x20, 0x61, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x20, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x45, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x20, 0x28, 0x44, 0x61, 0x74, 0x61, 0x29, 0x20, 0x46, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x20, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x2a, 0x11, 0x70, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, - 0x6a, 0x8b, 0x09, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, - 0x65, 0x73, 0x12, 0xf9, 0x08, 0x32, 0xf6, 0x08, 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, 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, 0xd3, 0x0b, 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, 0x83, 0x0b, 0x92, 0x41, 0xca, 0x0a, 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, 0x1a, - 0xe8, 0x04, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x73, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x20, 0x28, 0x75, 0x73, 0x65, 0x72, 0x73, 0x20, 0x61, - 0x6e, 0x64, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x73, 0x65, 0x74, 0x73, 0x29, 0x20, 0x74, 0x68, - 0x61, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x61, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x20, 0x6f, 0x72, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x67, 0x69, 0x76, 0x65, 0x6e, 0x20, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x20, 0x41, 0x50, 0x49, - 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x70, - 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x61, 0x20, 0x75, 0x73, - 0x65, 0x72, 0x20, 0x73, 0x65, 0x74, 0x20, 0x74, 0x72, 0x65, 0x65, 0x2c, 0x20, 0x77, 0x68, 0x6f, - 0x73, 0x65, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x20, 0x61, 0x72, - 0x65, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x49, 0x44, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x75, 0x73, - 0x65, 0x72, 0x20, 0x73, 0x65, 0x74, 0x73, 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, - 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0xe2, 0x9f, 0xa8, 0x6f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x23, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0xe2, 0x9f, 0xa9, 0x20, - 0x70, 0x61, 0x69, 0x72, 0x73, 0x2e, 0x0a, 0x20, 0x3c, 0x54, 0x69, 0x70, 0x3e, 0x57, 0x48, 0x45, - 0x4e, 0x20, 0x54, 0x4f, 0x20, 0x55, 0x53, 0x45, 0x20, 0x3f, 0x0a, 0x45, 0x78, 0x70, 0x61, 0x6e, - 0x64, 0x20, 0x69, 0x73, 0x20, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x20, 0x66, 0x6f, - 0x72, 0x20, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x73, 0x65, 0x74, 0x20, 0x6f, 0x66, 0x20, - 0x75, 0x73, 0x65, 0x72, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, - 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x69, 0x72, 0x20, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x2c, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x61, - 0x6c, 0x6c, 0x6f, 0x77, 0x73, 0x20, 0x6f, 0x75, 0x72, 0x20, 0x75, 0x73, 0x65, 0x72, 0x73, 0x20, - 0x74, 0x6f, 0x20, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x20, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, - 0x6e, 0x74, 0x20, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x65, - 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x2d, 0x63, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2e, - 0x0a, 0x20, 0x49, 0x74, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x64, 0x65, 0x73, 0x69, - 0x67, 0x6e, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x73, 0x20, 0x61, - 0x20, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x20, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x2e, 0x20, 0x45, - 0x78, 0x70, 0x61, 0x6e, 0x64, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x68, 0x61, - 0x73, 0x20, 0x61, 0x20, 0x68, 0x69, 0x67, 0x68, 0x20, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, - 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x63, 0x61, 0x75, 0x73, 0x65, - 0x20, 0x61, 0x20, 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x69, - 0x73, 0x73, 0x75, 0x65, 0x73, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x69, 0x74, 0x73, 0x20, 0x75, - 0x73, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x20, 0x63, 0x68, - 0x65, 0x63, 0x6b, 0x2e, 0x3c, 0x2f, 0x54, 0x69, 0x70, 0x3e, 0x2a, 0x12, 0x70, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x6a, 0x92, - 0x05, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, - 0x12, 0x80, 0x05, 0x32, 0xfd, 0x04, 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, 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, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x7b, + 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, 0x45, 0x78, 0x70, 0x61, 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, + 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, 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, 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, 0xf0, 0x09, 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, 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, 0x8e, 0x09, 0x92, 0x41, 0xce, - 0x08, 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, 0x1a, 0xb8, 0x01, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20, 0x45, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x6c, 0x65, - 0x74, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x73, 0x6b, 0x20, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6f, 0x66, 0x20, - 0xe2, 0x80, 0x9c, 0x57, 0x68, 0x69, 0x63, 0x68, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x75, 0x73, 0x65, 0x72, 0x3a, 0x58, 0x20, 0x64, 0x6f, - 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x59, 0x3f, 0xe2, 0x80, 0x9d, 0x2e, 0x20, 0x41, - 0x73, 0x20, 0x61, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, - 0x74, 0x68, 0x69, 0x73, 0x20, 0x79, 0x6f, 0x75, 0xe2, 0x80, 0x99, 0x6c, 0x6c, 0x20, 0x67, 0x65, - 0x74, 0x20, 0x61, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x6f, - 0x66, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 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, 0x6a, 0xc3, 0x06, 0x0a, 0x0d, 0x78, 0x2d, - 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xb1, 0x06, 0x32, 0xae, - 0x06, 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, 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, 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, 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, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x54, 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, 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, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x26, 0x20, 0x76, 0x31, + 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, 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, + 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, 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, 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, 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, 0xc8, 0x0e, 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, 0xd8, 0x0d, 0x92, 0x41, 0x91, 0x0d, - 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, 0x1a, 0xc3, 0x01, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20, 0x45, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x6c, 0x65, 0x74, - 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x73, 0x6b, 0x20, 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6f, 0x66, 0x20, 0xe2, - 0x80, 0x9c, 0x57, 0x68, 0x69, 0x63, 0x68, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x75, 0x73, 0x65, 0x72, 0x3a, 0x58, 0x20, 0x64, 0x6f, 0x20, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x59, 0x3f, 0xe2, 0x80, 0x9d, 0x2e, 0x20, 0x41, 0x73, - 0x20, 0x61, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, - 0x68, 0x69, 0x73, 0x20, 0x79, 0x6f, 0x75, 0xe2, 0x80, 0x99, 0x6c, 0x6c, 0x20, 0x67, 0x65, 0x74, - 0x20, 0x61, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x6f, 0x66, - 0x20, 0x61, 0x73, 0x20, 0x61, 0x20, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x20, - 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 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, 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, 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, + 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, 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, 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, + 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, 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, 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, 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, + 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, 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, + 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, 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, 0xf5, 0x0a, 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, 0x90, 0x0a, 0x92, 0x41, - 0xcf, 0x09, 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, 0x1a, 0xbe, 0x01, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20, 0x53, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, - 0x6c, 0x65, 0x74, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x61, 0x73, 0x6b, 0x20, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6f, - 0x66, 0x20, 0xe2, 0x80, 0x9c, 0x57, 0x68, 0x69, 0x63, 0x68, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x64, 0x6f, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x59, 0x20, 0x6f, 0x6e, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x58, 0x3f, - 0xe2, 0x80, 0x9d, 0x2e, 0x20, 0x41, 0x73, 0x20, 0x61, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x79, 0x6f, 0x75, 0xe2, 0x80, - 0x99, 0x6c, 0x6c, 0x20, 0x67, 0x65, 0x74, 0x20, 0x61, 0x20, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x66, - 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, - 0x61, 0x72, 0x72, 0x61, 0x79, 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, 0x6a, 0xbd, 0x07, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, - 0x6c, 0x65, 0x73, 0x12, 0xab, 0x07, 0x32, 0xa8, 0x07, 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, + 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, 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, 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, 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, 0xcd, - 0x0d, 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, - 0xdc, 0x0c, 0x92, 0x41, 0x97, 0x0c, 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, 0x1a, 0x81, 0x04, 0x54, 0x68, 0x65, 0x20, 0x53, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x20, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x20, 0x61, - 0x6c, 0x6c, 0x6f, 0x77, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x74, 0x6f, 0x20, 0x69, 0x6e, 0x71, - 0x75, 0x69, 0x72, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, - 0x20, 0x6f, 0x66, 0x20, 0xe2, 0x80, 0x9c, 0x57, 0x68, 0x69, 0x63, 0x68, 0x20, 0x70, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x75, 0x73, 0x65, 0x72, 0x3a, 0x78, 0x20, - 0x63, 0x61, 0x6e, 0x20, 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6f, 0x6e, 0x20, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x3a, 0x79, 0x3f, 0xe2, 0x80, 0x9d, 0x2e, 0x20, 0x49, 0x6e, 0x20, - 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2c, 0x20, 0x79, 0x6f, 0x75, 0x27, 0x6c, 0x6c, - 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, - 0x6f, 0x66, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x73, - 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x75, - 0x73, 0x65, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, 0x69, 0x76, 0x65, - 0x6e, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2c, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, - 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, - 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x6d, 0x61, 0x70, 0x2e, 0x20, 0x0a, 0x0a, 0x20, 0x49, 0x6e, - 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2c, 0x20, - 0x79, 0x6f, 0x75, 0x27, 0x6c, 0x6c, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, 0x61, - 0x20, 0x6d, 0x61, 0x70, 0x20, 0x6f, 0x66, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x69, 0x72, 0x20, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x65, 0x73, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6c, 0x79, 0x2e, - 0x20, 0x54, 0x68, 0x65, 0x20, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x20, 0x69, - 0x73, 0x20, 0x6d, 0x61, 0x70, 0x5b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5d, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2c, 0x20, 0x73, 0x75, 0x63, 0x68, 0x20, 0x61, - 0x73, 0x20, 0x22, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x20, 0x2d, 0x3e, 0x20, 0x22, 0x41, 0x4c, 0x4c, 0x4f, 0x57, 0x45, - 0x44, 0x22, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, - 0x6e, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x69, 0x72, 0x20, 0x61, 0x73, - 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x64, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, 0x20, - 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6b, 0x65, 0x79, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x70, - 0x61, 0x69, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 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, 0x6a, 0xb0, 0x07, 0x0a, 0x0d, 0x78, - 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0x9e, 0x07, 0x32, - 0x9b, 0x07, 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, + 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, 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, 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, 0xd6, - 0x08, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0xcc, 0x08, 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, 0x91, 0x08, 0x92, 0x41, 0xe5, 0x07, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x1a, - 0x60, 0x54, 0x68, 0x65, 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x20, 0x57, 0x61, 0x74, - 0x63, 0x68, 0x20, 0x41, 0x50, 0x49, 0x20, 0x61, 0x63, 0x74, 0x73, 0x20, 0x61, 0x73, 0x20, 0x61, - 0x20, 0x72, 0x65, 0x61, 0x6c, 0x2d, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x62, 0x72, 0x6f, 0x61, 0x64, - 0x63, 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x73, 0x68, 0x6f, 0x77, - 0x73, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, - 0x2e, 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, 0xca, 0x19, 0x0a, 0x06, 0x53, 0x63, 0x68, 0x65, - 0x6d, 0x61, 0x12, 0xb7, 0x0d, 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, 0xf2, 0x0c, 0x92, 0x41, 0xbe, 0x0c, 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, 0x1a, 0xd2, 0x01, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, - 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x20, 0x69, 0x74, 0x27, 0x73, 0x20, 0x6f, 0x77, - 0x6e, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, - 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x6f, 0x64, 0x65, - 0x6c, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, - 0x73, 0x20, 0x6f, 0x66, 0x20, 0x65, 0x61, 0x73, 0x69, 0x6c, 0x79, 0x2e, 0x20, 0x57, 0x65, 0x20, - 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x20, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x20, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x20, 0x61, 0x6e, - 0x64, 0x20, 0x69, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x6f, 0x75, 0x72, 0x20, 0x70, 0x6c, 0x61, 0x79, 0x67, - 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x61, 0x73, 0x20, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x61, 0x73, - 0x20, 0x69, 0x6e, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x49, 0x44, 0x45, 0x20, 0x6f, 0x72, 0x20, 0x74, - 0x65, 0x78, 0x74, 0x20, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x2e, 0x2a, 0x0d, 0x73, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x73, 0x2e, 0x77, 0x72, 0x69, 0x74, 0x65, 0x6a, 0xaf, 0x0a, 0x0a, 0x0d, 0x78, - 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0x9d, 0x0a, 0x32, - 0x9a, 0x0a, 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, 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, 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, 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, + 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, 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, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x2a, 0x3a, 0x01, 0x2a, 0x22, 0x25, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x65, 0x6e, 0x61, 0x6e, + 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, 0x77, 0x72, 0x69, 0x74, 0x65, 0x12, 0xeb, 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, 0xa9, - 0x06, 0x92, 0x41, 0xf6, 0x05, 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, 0x1a, 0xa2, 0x01, 0x57, - 0x68, 0x65, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x20, 0x69, 0x73, 0x20, 0x77, - 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x66, - 0x79, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x20, 0x41, 0x50, 0x49, 0x20, 0x61, 0x20, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x69, - 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x20, 0x62, - 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x41, 0x50, 0x49, 0x2e, 0x20, 0x54, 0x68, 0x61, 0x74, 0x20, - 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x63, - 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x69, 0x6e, - 0x73, 0x70, 0x65, 0x63, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, - 0x2e, 0x2a, 0x0c, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2e, 0x72, 0x65, 0x61, 0x64, 0x6a, - 0x99, 0x04, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, - 0x73, 0x12, 0x87, 0x04, 0x32, 0x84, 0x04, 0x0a, 0xf6, 0x01, 0x2a, 0xf3, 0x01, 0x0a, 0x0d, 0x0a, + 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, 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, 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, 0xd3, 0x01, 0x0a, 0x06, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xc8, 0x01, 0x1a, 0xc5, 0x01, 0x73, 0x72, 0x2c, 0x20, 0x65, + 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, 0x52, 0x65, 0x61, 0x64, 0x28, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, + 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, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, + 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, 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, 0x88, 0x02, 0x2a, 0x85, 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, 0xdb, 0x01, - 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xd0, 0x01, 0x1a, 0xcd, 0x01, 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, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 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, 0x20, 0x20, 0x20, 0x20, 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, 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, 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, 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, 0x29, 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, 0x97, 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, 0xd5, 0x04, 0x92, - 0x41, 0xa2, 0x04, 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, 0x1a, 0x87, 0x01, 0x4d, 0x6f, 0x64, - 0x65, 0x6c, 0x73, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x20, 0x41, 0x50, - 0x49, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x64, 0x20, - 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x41, 0x50, 0x49, 0x20, 0x77, - 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x73, 0x20, 0x61, 0x74, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x20, 0x77, 0x65, 0x72, 0x65, 0x20, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x2e, 0x2a, 0x0c, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2e, 0x6c, 0x69, - 0x73, 0x74, 0x6a, 0xe0, 0x02, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x73, 0x12, 0xce, 0x02, 0x32, 0xcb, 0x02, 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, 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, 0xcd, 0x36, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x93, 0x10, - 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, 0xd2, - 0x0f, 0x92, 0x41, 0xa1, 0x0f, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0b, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x20, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xa6, 0x01, 0x49, 0x6e, 0x20, 0x50, 0x65, - 0x72, 0x6d, 0x69, 0x66, 0x79, 0x2c, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, - 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, - 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x65, 0x6e, 0x74, - 0x69, 0x74, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x20, 0x61, - 0x6e, 0x64, 0x20, 0x75, 0x73, 0x65, 0x72, 0x73, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, - 0x6e, 0x74, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x20, 0x54, 0x68, 0x65, - 0x73, 0x65, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x61, - 0x73, 0x20, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x70, 0x72, - 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x20, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, - 0x2e, 0x2a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x77, 0x72, 0x69, 0x74, 0x65, 0x6a, 0xd6, 0x0d, - 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, - 0xc4, 0x0d, 0x32, 0xc1, 0x0d, 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, + 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, 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, 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, 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, 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, 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, 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, + 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, 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, 0xe8, 0x09, 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, + 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, 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, 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, 0x8d, 0x09, 0x92, - 0x41, 0xcf, 0x08, 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, 0x1a, 0x6b, 0x52, 0x65, 0x61, 0x64, 0x20, 0x41, 0x50, 0x49, 0x20, 0x61, 0x6c, 0x6c, 0x6f, - 0x77, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6c, 0x79, 0x20, - 0x71, 0x75, 0x65, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x6f, - 0x72, 0x65, 0x64, 0x20, 0x67, 0x72, 0x61, 0x70, 0x68, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x74, - 0x6f, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x66, 0x69, - 0x6c, 0x74, 0x65, 0x72, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x72, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x2e, 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, 0xa8, 0x07, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, - 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0x96, 0x07, 0x32, 0x93, 0x07, 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, 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, 0xb5, 0x08, 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, 0xe3, - 0x07, 0x92, 0x41, 0xa8, 0x07, 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, 0x1a, 0x64, - 0x52, 0x65, 0x61, 0x64, 0x20, 0x41, 0x50, 0x49, 0x20, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x73, 0x20, - 0x66, 0x6f, 0x72, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6c, 0x79, 0x20, 0x71, 0x75, 0x65, - 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, - 0x20, 0x67, 0x72, 0x61, 0x70, 0x68, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x74, 0x6f, 0x20, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x66, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x73, 0x2e, 0x2a, 0x14, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x61, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x72, 0x65, 0x61, 0x64, 0x6a, 0x90, 0x06, 0x0a, 0x0d, 0x78, - 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xfe, 0x05, 0x32, - 0xfb, 0x05, 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, + 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, 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, 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, 0x86, 0x09, 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, 0xc2, 0x08, 0x92, 0x41, 0x90, 0x08, 0x0a, - 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0b, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x64, 0x61, - 0x74, 0x61, 0x1a, 0x4b, 0x59, 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x64, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x72, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x20, 0x6f, 0x72, - 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, - 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x20, 0x41, 0x50, 0x49, 0x2e, 0x2a, - 0x0b, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x6a, 0xa0, 0x07, 0x0a, - 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0x8e, - 0x07, 0x32, 0x8b, 0x07, 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, + 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, 0x97, 0x03, 0x2a, 0x94, 0x03, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, + 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, 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, + 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, 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, + 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, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x68, 0x69, 0x70, 0x73, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0xe5, 0x07, 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, 0xa0, 0x07, 0x92, 0x41, 0xea, 0x06, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0a, - 0x72, 0x75, 0x6e, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x1a, 0xfc, 0x01, 0x54, 0x68, 0x65, - 0x20, 0x22, 0x52, 0x75, 0x6e, 0x20, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0x20, 0x41, 0x50, - 0x49, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x74, 0x72, - 0x61, 0x69, 0x67, 0x68, 0x74, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x20, 0x77, 0x61, 0x79, - 0x20, 0x74, 0x6f, 0x20, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x20, 0x70, 0x72, 0x65, 0x64, - 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x20, 0x77, - 0x69, 0x74, 0x68, 0x69, 0x6e, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x27, 0x73, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, - 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x20, 0x42, 0x79, 0x20, - 0x73, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x65, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2c, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, - 0x20, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, - 0x69, 0x63, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x69, - 0x65, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x20, - 0x65, 0x6e, 0x63, 0x61, 0x70, 0x73, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, - 0x61, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x2a, 0x0a, 0x62, 0x75, 0x6e, 0x64, 0x6c, - 0x65, 0x2e, 0x72, 0x75, 0x6e, 0x6a, 0xca, 0x04, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, - 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xb8, 0x04, 0x32, 0xb5, 0x04, 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, 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, 0x90, 0x1d, 0x0a, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, - 0xbc, 0x10, 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, 0xf7, 0x0f, 0x92, 0x41, 0xc4, 0x0f, 0x0a, 0x06, 0x42, 0x75, 0x6e, - 0x64, 0x6c, 0x65, 0x12, 0x0c, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, - 0x65, 0x1a, 0x91, 0x02, 0x54, 0x68, 0x65, 0x20, 0x22, 0x57, 0x72, 0x69, 0x74, 0x65, 0x20, 0x42, - 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0x20, 0x41, 0x50, 0x49, 0x20, 0x69, 0x73, 0x20, 0x64, 0x65, - 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x6c, - 0x69, 0x6e, 0x67, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x75, - 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, - 0x6e, 0x74, 0x2e, 0x20, 0x49, 0x74, 0x73, 0x20, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, - 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x77, - 0x72, 0x69, 0x74, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, - 0x64, 0x61, 0x74, 0x61, 0x20, 0x61, 0x63, 0x63, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x74, - 0x6f, 0x20, 0x70, 0x72, 0x65, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x73, 0x74, 0x72, - 0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x41, 0x50, - 0x49, 0x20, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x73, 0x20, 0x75, 0x73, 0x65, 0x72, 0x73, 0x20, 0x74, - 0x6f, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x6f, 0x72, 0x20, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x2c, - 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x64, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x75, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x61, 0x20, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, - 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x2a, 0x0c, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x6a, 0x89, 0x0d, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, - 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xf7, 0x0c, 0x32, 0xf4, 0x0c, 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, + 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, 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, 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, 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, @@ -5597,246 +5655,360 @@ var file_base_v1_service_proto_rawDesc = []byte{ 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, 0x7d, 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, 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, 0xa1, - 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, 0xdf, 0x05, 0x92, 0x41, 0xad, 0x05, 0x0a, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, - 0x0b, 0x72, 0x65, 0x61, 0x64, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x1a, 0xfd, 0x01, 0x54, - 0x68, 0x65, 0x20, 0x22, 0x52, 0x65, 0x61, 0x64, 0x20, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, - 0x20, 0x41, 0x50, 0x49, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x63, 0x72, 0x75, 0x63, 0x69, 0x61, - 0x6c, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, 0x74, 0x72, 0x69, - 0x65, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x20, 0x6f, 0x66, - 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x62, - 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x75, 0x6c, 0x74, - 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x65, 0x74, 0x75, 0x70, 0x2e, 0x20, 0x49, 0x74, 0x20, 0x69, - 0x73, 0x20, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2c, - 0x20, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x6c, 0x79, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x69, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x69, 0x74, 0x73, 0x20, 0x6e, 0x61, 0x6d, 0x65, - 0x2c, 0x20, 0x77, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x70, 0x65, - 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x27, 0x73, 0x20, - 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x2a, 0x0b, 0x62, 0x75, - 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x72, 0x65, 0x61, 0x64, 0x6a, 0x88, 0x03, 0x0a, 0x0d, 0x78, 0x2d, - 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xf6, 0x02, 0x32, 0xf3, - 0x02, 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, + 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, 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, + 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, 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, 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, 0xa2, 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, 0xda, 0x05, 0x92, 0x41, 0xa6, - 0x05, 0x0a, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x0d, 0x64, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x1a, 0xeb, 0x01, 0x54, 0x68, 0x65, 0x20, 0x22, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0x20, 0x41, - 0x50, 0x49, 0x20, 0x69, 0x73, 0x20, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x20, 0x66, - 0x6f, 0x72, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x73, 0x70, 0x65, 0x63, - 0x69, 0x66, 0x69, 0x63, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, - 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, - 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x20, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x41, 0x50, 0x49, 0x20, 0x66, 0x61, 0x63, 0x69, 0x6c, 0x69, - 0x74, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x69, - 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2c, 0x20, - 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x69, 0x74, - 0x73, 0x20, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x20, 0x66, - 0x72, 0x6f, 0x6d, 0x20, 0x61, 0x20, 0x64, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x65, 0x64, - 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x27, 0x73, 0x20, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, - 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x2a, 0x0d, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x64, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x6a, 0x8f, 0x03, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, - 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xfd, 0x02, 0x32, 0xfa, 0x02, 0x0a, 0xbc, 0x01, - 0x2a, 0xb9, 0x01, 0x0a, 0x0d, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x04, 0x1a, 0x02, + 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, 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, 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, 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, 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, 0xe8, 0x0d, 0x0a, 0x07, 0x54, 0x65, 0x6e, 0x61, - 0x6e, 0x63, 0x79, 0x12, 0xec, 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, 0xa4, 0x05, 0x92, 0x41, - 0x83, 0x05, 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, 0x1a, 0x85, - 0x02, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x66, 0x79, 0x20, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x20, 0x54, - 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x79, - 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x63, 0x75, - 0x73, 0x74, 0x6f, 0x6d, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x20, 0x66, 0x6f, 0x72, - 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x20, 0x74, 0x68, 0x65, 0x6d, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x73, 0x69, - 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x2e, 0x20, 0x59, 0x6f, 0x75, 0x20, - 0x63, 0x61, 0x6e, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x74, 0x65, 0x6e, - 0x61, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, - 0x6e, 0x67, 0x20, 0x41, 0x50, 0x49, 0x2e, 0x0a, 0x20, 0x3c, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, - 0x67, 0x3e, 0x57, 0x65, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x61, 0x20, 0x70, 0x72, 0x65, 0x2d, - 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x20, - 0x2d, 0x20, 0x74, 0x31, 0x20, 0x2d, 0x20, 0x62, 0x79, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, - 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x6e, 0x65, 0x73, 0x20, 0x74, - 0x68, 0x61, 0x74, 0x20, 0x64, 0x6f, 0x6e, 0x27, 0x74, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6d, 0x75, - 0x6c, 0x74, 0x69, 0x2d, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x2e, 0x3c, 0x2f, 0x57, 0x61, - 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x3e, 0x2a, 0x0e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2e, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x6a, 0xcc, 0x02, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, - 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xba, 0x02, 0x32, 0xb7, 0x02, 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, 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, 0xef, 0x03, 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, 0xa7, 0x03, 0x92, 0x41, 0x8b, - 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, 0x1a, 0x2b, 0x59, 0x6f, 0x75, 0x20, 0x63, - 0x61, 0x6e, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x61, 0x20, 0x74, 0x65, 0x6e, 0x61, - 0x6e, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, - 0x67, 0x20, 0x41, 0x50, 0x49, 0x2e, 0x2a, 0x0e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2e, - 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x6a, 0xb3, 0x02, 0x0a, 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, - 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xa1, 0x02, 0x32, 0x9e, 0x02, 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, 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, 0xfb, 0x03, 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, 0xb9, 0x03, 0x92, 0x41, 0x9a, 0x03, 0x0a, 0x07, 0x54, - 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x12, 0x0c, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x74, 0x65, 0x6e, - 0x61, 0x6e, 0x74, 0x73, 0x1a, 0x28, 0x59, 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x6c, 0x69, - 0x73, 0x74, 0x20, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, - 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x20, 0x41, 0x50, 0x49, 0x2e, 0x2a, 0x0c, - 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x73, 0x2e, 0x6c, 0x69, 0x73, 0x74, 0x6a, 0xc8, 0x02, 0x0a, - 0x0d, 0x78, 0x2d, 0x63, 0x6f, 0x64, 0x65, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x12, 0xb6, - 0x02, 0x32, 0xb3, 0x02, 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, 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, + 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/proto/base/v1/service.proto b/proto/base/v1/service.proto index 7ff3cc140..5042965ca 100644 --- a/proto/base/v1/service.proto +++ b/proto/base/v1/service.proto @@ -24,12 +24,12 @@ 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: "In Permify, you can perform two different types access checks,\n\n resource based authorization checks, in form of Can user U perform action Y in resource Z?\nsubject based authorization checks,\n\n in form of Which resources can user U edit?\nIn this section we'll look at the resource based check request of Permify.\nYou can find subject based access checks in Entity (Data) Filtering section.", + description: "", extensions: { key: "x-codeSamples" value: { @@ -65,6 +65,22 @@ service Permission { 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}'"}, + } + } } } } @@ -82,12 +98,12 @@ 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: "Retrieve all subjects (users and user sets) that have a relationship or attribute with given entity and permission.\nExpand API response is represented by a user set tree, whose leaf nodes are user IDs or user sets pointing to other ⟨object#relation⟩ pairs.\n WHEN TO USE ?\nExpand 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.\n 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.", + description: "", extensions: { key: "x-codeSamples" value: { @@ -123,6 +139,22 @@ service Permission { 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}'"}, + } + } } } } @@ -140,12 +172,12 @@ 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: "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." + description: "" extensions: { key: "x-codeSamples" value: { @@ -181,6 +213,22 @@ service Permission { 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}'"}, + } + } } } } @@ -198,12 +246,12 @@ 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: "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 as a streaming response." + description: "" extensions: { key: "x-codeSamples" value: { @@ -256,12 +304,12 @@ 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: "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." + description: "" extensions: { key: "x-codeSamples" value: { @@ -297,6 +345,22 @@ service Permission { 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}'"}, + } + } } } } @@ -314,12 +378,12 @@ 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: "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. \n\n 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." + description: "" extensions: { key: "x-codeSamples" value: { @@ -355,6 +419,22 @@ service Permission { 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}'"}, + } + } } } } @@ -693,12 +773,12 @@ 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: "The Permify Watch API acts as a real-time broadcaster that shows changes in the relation tuples." + description: "" extensions: { key: "x-codeSamples" value: { @@ -785,10 +865,10 @@ 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: "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 as well as in any IDE or text editor." + description: "" extensions: { key: "x-codeSamples" value: { @@ -824,6 +904,22 @@ service Schema { 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}'"}, + } + } } } } @@ -843,10 +939,10 @@ 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: "When a model is written to Permify using the write schema API a schema version will be returned by the API. That schema version can be used to inspect the schema." + description: "" extensions: { key: "x-codeSamples" value: { @@ -879,7 +975,23 @@ service Schema { } fields: { key: "source" - value : { string_value: "let res = client.schema.read({\n tenantId: \"t1\",\n metadata: {\n schemaVersion: swResponse.schemaVersion,\n },\n })"}, + 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}'"}, } } } @@ -901,10 +1013,10 @@ 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: "Models written to Permify using the write schema API can be listed using this API with the timestamps at which the models were created." + description: "" extensions: { key: "x-codeSamples" value: { @@ -940,6 +1052,22 @@ service Schema { 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}'"}, + } + } } } } @@ -1065,12 +1193,12 @@ service Data { }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "create data" + summary: "write data" tags: [ "Data" ] operation_id: "data.write" - description: "In Permify, attributes and relations between your entities, objects and users represents your authorization data. These data stored as tuples in a preferred database." + description: "" extensions: { key: "x-codeSamples" value: { @@ -1106,6 +1234,22 @@ service Data { 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}'"}, + } + } } } } @@ -1121,7 +1265,7 @@ service Data { }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - summary: "create new relationships" + summary: "write relationships" tags: [ "Data" ] @@ -1137,12 +1281,12 @@ 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: "Read API allows for directly querying the stored graph data to display and filter stored relational tuples." + description: "" extensions: { key: "x-codeSamples" value: { @@ -1178,6 +1322,22 @@ service Data { 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}'"}, + } + } } } } @@ -1193,12 +1353,12 @@ 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: "Read API allows for directly querying the stored graph data to display and filter stored attributes." + description: "" extensions: { key: "x-codeSamples" value: { @@ -1234,6 +1394,22 @@ service Data { 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}'"}, + } + } } } } @@ -1254,7 +1430,7 @@ service Data { "Data" ] operation_id: "data.delete" - description: "You can delete any stored relation tuples or attributes with following API." + description: "" extensions: { key: "x-codeSamples" value: { @@ -1290,6 +1466,22 @@ service Data { 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}'"}, + } + } } } } @@ -1326,7 +1518,7 @@ service Data { "Data" ] operation_id: "bundle.run" - description: "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." + description: "" extensions: { key: "x-codeSamples" value: { @@ -1362,6 +1554,22 @@ service Data { 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}'"}, + } + } } } } @@ -1622,7 +1830,7 @@ service Bundle { "Bundle" ] operation_id: "bundle.write" - description: "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." + description: "" extensions: { key: "x-codeSamples" value: { @@ -1658,6 +1866,22 @@ service Bundle { 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}'"}, + } + } } } } @@ -1678,7 +1902,7 @@ service Bundle { "Bundle" ] operation_id: "bundle.read" - description: "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." + description: "" extensions: { key: "x-codeSamples" value: { @@ -1714,6 +1938,22 @@ service Bundle { 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}'"}, + } + } } } } @@ -1734,7 +1974,7 @@ service Bundle { "Bundle" ] operation_id: "bundle.delete" - description: "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." + description: "" extensions: { key: "x-codeSamples" value: { @@ -1770,6 +2010,22 @@ service Bundle { 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}'"}, + } + } } } } @@ -1839,12 +2095,12 @@ 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: "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.\n We have a pre-inserted tenant - t1 - by default for the ones that don't use multi-tenancy.", + description: "", extensions: { key: "x-codeSamples" value: { @@ -1880,6 +2136,22 @@ service Tenancy { 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}'"}, + } + } } } } @@ -1900,7 +2172,7 @@ service Tenancy { "Tenancy" ] operation_id: "tenants.delete" - description: "You can delete a tenant with following API.", + description: "", extensions: { key: "x-codeSamples" value: { @@ -1936,6 +2208,22 @@ service Tenancy { 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'"}, + } + } } } } @@ -1957,7 +2245,7 @@ service Tenancy { "Tenancy" ] operation_id: "tenants.list" - description: "You can list tenants with following API.", + description: "", extensions: { key: "x-codeSamples" value: { @@ -1993,6 +2281,22 @@ service Tenancy { 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}'"}, + } + } } } } From 80f6c4e6c555e610252de9b8c70687db11358340 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 05:31:19 +0000 Subject: [PATCH 46/70] build(deps): bump github/codeql-action from 3.24.7 to 3.24.8 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.24.7 to 3.24.8. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/3ab4101902695724f9365a384f86c1074d94e18c...05963f47d870e2cb19a537396c1f668a348c7d8f) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- .github/workflows/scorecard.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1142fdae4..a12f9f354 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@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 + 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@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 + 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@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 + uses: github/codeql-action/analyze@05963f47d870e2cb19a537396c1f668a348c7d8f # v3.24.8 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 6ea6c196f..8890c1e4a 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@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 + uses: github/codeql-action/upload-sarif@05963f47d870e2cb19a537396c1f668a348c7d8f # v3.24.8 with: sarif_file: results.sarif From c8059e9be78918ae25040f3f9bb20ca4d92f4ab6 Mon Sep 17 00:00:00 2001 From: ahmethakanbesel Date: Tue, 19 Mar 2024 12:07:41 +0300 Subject: [PATCH 47/70] authn/oidc: add backoff strategy --- internal/authn/oidc/authn.go | 58 ++++++++++++++++++++++++++++-------- internal/config/config.go | 7 +++-- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/internal/authn/oidc/authn.go b/internal/authn/oidc/authn.go index 08ff327fb..84d917eb9 100644 --- a/internal/authn/oidc/authn.go +++ b/internal/authn/oidc/authn.go @@ -40,6 +40,21 @@ type Authn struct { // httpClient is used to make HTTP requests, e.g., to fetch the JWKS. httpClient *http.Client + + // Last time the JWKS was fetched + lastKeyFetch time.Time + + // Last time the OIDC configuration was fetched + lastOIDCConfigFetch time.Time + + // KeyRefreshInterval is the interval to refresh the keys + keyRefreshInterval time.Duration + + // ConfigRefreshInterval is the interval to refresh the OIDC configuration + configRefreshInterval time.Duration + + // autoFetchKeysOnTokenNotFound is a flag to enable/disable auto fetching of keys when token is not found in the header + autoFetchKeysOnTokenNotFound bool } // NewOidcAuthn creates a new instance of Authn configured for OIDC authentication. @@ -54,13 +69,14 @@ func NewOidcAuthn(_ context.Context, conf config.Oidc) (*Authn, error) { // 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. oidc := &Authn{ - IssuerURL: conf.Issuer, - Audience: conf.Audience, - httpClient: client.StandardClient(), // Wrap retryable client as a standard http.Client + IssuerURL: conf.Issuer, + Audience: conf.Audience, + httpClient: client.StandardClient(), // Wrap retryable client as a standard http.Client + keyRefreshInterval: conf.KeyRefreshInterval, + configRefreshInterval: conf.ConfigRefreshInterval, + autoFetchKeysOnTokenNotFound: conf.AutoFetchKeysOnTokenNotFound, } - // 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() if err != nil { // If fetching keys fails, return an error to prevent initialization of a non-functional Authn instance. @@ -85,6 +101,20 @@ func (oidc *Authn) Authenticate(requestContext context.Context) error { // Parse and validate the JWT from the authentication header. token, err := jwtParser.Parse(authHeader, func(token *jwt.Token) (any, error) { + // If a presented token's KID is not found in the existing headers, initiate a JWKs fetch and validate the token. + if _, ok := token.Header["kid"].(string); !ok { + // Whem KID is absent in the header and it has been less than defaultKeyRefreshInterval since the last JWKs retrieval attempt, reject the token. + if !oidc.autoFetchKeysOnTokenNotFound && time.Since(oidc.lastKeyFetch) < oidc.keyRefreshInterval { + return nil, errors.New(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()) + } + + // Fetch the JWKS keys from the OIDC provider. + err := oidc.fetchKeys() + if err != nil { + return nil, err + } + } + // Use the JWKS from oidc to validate the JWT's signature. return oidc.JWKs.Keyfunc(token) }) @@ -118,19 +148,23 @@ func (oidc *Authn) Authenticate(requestContext context.Context) error { } func (oidc *Authn) fetchKeys() error { - oidcConfig, err := oidc.fetchOIDCConfiguration() - if err != nil { - return fmt.Errorf("error fetching OIDC configuration: %w", err) + if oidc.JwksURI == "" || time.Since(oidc.lastOIDCConfigFetch) > oidc.configRefreshInterval { + oidcConfig, err := oidc.fetchOIDCConfiguration() + if err != nil { + return fmt.Errorf("error fetching OIDC configuration: %w", err) + } + + oidc.JwksURI = oidcConfig.JWKsURI + oidc.lastOIDCConfigFetch = time.Now() } - oidc.JwksURI = oidcConfig.JWKsURI - jwks, err := oidc.GetKeys() if err != nil { return fmt.Errorf("error fetching OIDC keys: %w", err) } oidc.JWKs = jwks + oidc.lastKeyFetch = time.Now() return nil } @@ -141,8 +175,8 @@ func (oidc *Authn) GetKeys() (*keyfunc.JWKS, error) { // 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. + Client: oidc.httpClient, // Use the HTTP client configured in the Authn struct. + RefreshInterval: oidc.keyRefreshInterval, // 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) diff --git a/internal/config/config.go b/internal/config/config.go index 721436cb8..0a54c64f7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -69,8 +69,11 @@ 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 + ConfigRefreshInterval time.Duration `mapstructure:"config_refresh_interval"` // Interval to refresh the OIDC configuration + KeyRefreshInterval time.Duration `mapstructure:"key_refresh_interval"` // Interval to refresh the keys + AutoFetchKeysOnTokenNotFound bool `mapstructure:"auto_fetch_keys_on_token_not_found"` // Flag to enable/disable auto fetching of keys when token is not found in the header } // Profiler contains configuration for the profiler. From 5644d7bb18138331338dcc85800ecc5cb7e699e1 Mon Sep 17 00:00:00 2001 From: ahmethakanbesel Date: Tue, 19 Mar 2024 12:49:13 +0300 Subject: [PATCH 48/70] authn/oidc: refactor `KID` backoff mechanism --- internal/authn/oidc/authn.go | 29 ++++++++++++----------------- internal/config/config.go | 10 +++++----- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/internal/authn/oidc/authn.go b/internal/authn/oidc/authn.go index 84d917eb9..9e539f801 100644 --- a/internal/authn/oidc/authn.go +++ b/internal/authn/oidc/authn.go @@ -53,8 +53,8 @@ type Authn struct { // ConfigRefreshInterval is the interval to refresh the OIDC configuration configRefreshInterval time.Duration - // autoFetchKeysOnTokenNotFound is a flag to enable/disable auto fetching of keys when token is not found in the header - autoFetchKeysOnTokenNotFound bool + // refreshUnknownKID is a flag to refresh the JWKS when the KID is unknown + refreshUnknownKID bool } // NewOidcAuthn creates a new instance of Authn configured for OIDC authentication. @@ -69,12 +69,12 @@ func NewOidcAuthn(_ context.Context, conf config.Oidc) (*Authn, error) { // 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. oidc := &Authn{ - IssuerURL: conf.Issuer, - Audience: conf.Audience, - httpClient: client.StandardClient(), // Wrap retryable client as a standard http.Client - keyRefreshInterval: conf.KeyRefreshInterval, - configRefreshInterval: conf.ConfigRefreshInterval, - autoFetchKeysOnTokenNotFound: conf.AutoFetchKeysOnTokenNotFound, + IssuerURL: conf.Issuer, + Audience: conf.Audience, + httpClient: client.StandardClient(), // Wrap retryable client as a standard http.Client + keyRefreshInterval: conf.KeyRefreshInterval, + configRefreshInterval: conf.ConfigRefreshInterval, + refreshUnknownKID: conf.RefreshUnknownKID, } err := oidc.fetchKeys() @@ -104,15 +104,9 @@ func (oidc *Authn) Authenticate(requestContext context.Context) error { // If a presented token's KID is not found in the existing headers, initiate a JWKs fetch and validate the token. if _, ok := token.Header["kid"].(string); !ok { // Whem KID is absent in the header and it has been less than defaultKeyRefreshInterval since the last JWKs retrieval attempt, reject the token. - if !oidc.autoFetchKeysOnTokenNotFound && time.Since(oidc.lastKeyFetch) < oidc.keyRefreshInterval { + if !oidc.refreshUnknownKID && time.Since(oidc.lastKeyFetch) < oidc.keyRefreshInterval { return nil, errors.New(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()) } - - // Fetch the JWKS keys from the OIDC provider. - err := oidc.fetchKeys() - if err != nil { - return nil, err - } } // Use the JWKS from oidc to validate the JWT's signature. @@ -175,8 +169,9 @@ func (oidc *Authn) GetKeys() (*keyfunc.JWKS, error) { // 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: oidc.keyRefreshInterval, // Set the interval to refresh the keys every 48 hours. + Client: oidc.httpClient, // Use the HTTP client configured in the Authn struct. + RefreshInterval: oidc.keyRefreshInterval, // Set the interval to refresh the keys. + RefreshUnknownKID: oidc.refreshUnknownKID, // Set the flag to refresh the JWKS when the KID is unknown. }) if err != nil { return nil, fmt.Errorf("failed to fetch keys from '%s': %s", oidc.JwksURI, err) diff --git a/internal/config/config.go b/internal/config/config.go index 0a54c64f7..2ce0f4bb1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -69,11 +69,11 @@ type ( // Oidc contains configuration for OIDC authentication. Oidc struct { - Issuer string `mapstructure:"issuer"` // OIDC issuer URL - Audience string `mapstructure:"audience"` // OIDC client ID - ConfigRefreshInterval time.Duration `mapstructure:"config_refresh_interval"` // Interval to refresh the OIDC configuration - KeyRefreshInterval time.Duration `mapstructure:"key_refresh_interval"` // Interval to refresh the keys - AutoFetchKeysOnTokenNotFound bool `mapstructure:"auto_fetch_keys_on_token_not_found"` // Flag to enable/disable auto fetching of keys when token is not found in the header + Issuer string `mapstructure:"issuer"` // OIDC issuer URL + Audience string `mapstructure:"audience"` // OIDC client ID + ConfigRefreshInterval time.Duration `mapstructure:"config_refresh_interval"` // Interval to refresh the OIDC configuration + KeyRefreshInterval time.Duration `mapstructure:"key_refresh_interval"` // Interval to refresh the keys + RefreshUnknownKID bool `mapstructure:"refresh_unknown_kid"` // Flag to enable/disable auto fetching of keys when KID is not found in the header } // Profiler contains configuration for the profiler. From 6ef74f16991654c11c0e80b8dfb344c9602f2c31 Mon Sep 17 00:00:00 2001 From: EgeAytin Date: Tue, 19 Mar 2024 14:16:12 +0300 Subject: [PATCH 49/70] docs: clear the docusaurus docs --- docs/.DS_Store | Bin 0 -> 6148 bytes docs/.gitignore | 21 - docs/LICENSE | 201 - docs/README.md | 10 - docs/apidocs.swagger.json | 3329 ------ docs/babel.config.js | 3 - docs/client-development-en/0.pack | Bin 122933 -> 0 bytes docs/client-development-en/index.pack | Bin 1223337 -> 0 bytes docs/docs/api-overview.md | 93 - docs/docs/api-overview/_category_.json | 6 - docs/docs/api-overview/bundle/_category_.json | 5 - .../docs/api-overview/bundle/delete-bundle.md | 58 - docs/docs/api-overview/bundle/read-bundle.md | 58 - docs/docs/api-overview/bundle/write-bundle.md | 117 - docs/docs/api-overview/data/_category_.json | 5 - docs/docs/api-overview/data/delete-data.md | 111 - .../docs/api-overview/data/read-attributes.md | 95 - .../api-overview/data/read-relationships.md | 106 - docs/docs/api-overview/data/run-bundle.md | 75 - docs/docs/api-overview/data/write-data.md | 477 - .../api-overview/permission/_category_.json | 5 - .../docs/api-overview/permission/check-api.md | 197 - .../api-overview/permission/expand-api.md | 319 - .../api-overview/permission/lookup-entity.md | 228 - .../api-overview/permission/lookup-subject.md | 116 - .../permission/subject-permission.md | 133 - docs/docs/api-overview/schema/_category_.json | 6 - docs/docs/api-overview/schema/list-schema.md | 54 - docs/docs/api-overview/schema/read-schema.md | 55 - docs/docs/api-overview/schema/write-schema.md | 97 - .../docs/api-overview/tenancy/_category_.json | 5 - .../api-overview/tenancy/create-tenant.md | 59 - .../api-overview/tenancy/delete-tenant.md | 47 - docs/docs/api-overview/watch/_category_.json | 5 - docs/docs/api-overview/watch/watch-changes.md | 145 - docs/docs/bundle.md | 84 - docs/docs/comparision.md | 38 - docs/docs/examples.md | 18 - docs/docs/getting-started/_category_.json | 5 - docs/docs/getting-started/enforcement.md | 80 - .../getting-started/examples/_category_.json | 6 - .../examples/facebook-groups.md | 546 - .../getting-started/examples/google-docs.md | 344 - .../getting-started/examples/instagram.md | 376 - docs/docs/getting-started/examples/mercury.md | 157 - docs/docs/getting-started/examples/notion.md | 548 - docs/docs/getting-started/modeling.md | 613 -- docs/docs/getting-started/sync-data.md | 480 - docs/docs/getting-started/testing.md | 279 - docs/docs/installation.md | 17 - docs/docs/installation/_category_.json | 5 - docs/docs/installation/aws.md | 187 - docs/docs/installation/azure.md | 7 - docs/docs/installation/brew.md | 66 - docs/docs/installation/container.md | 55 - docs/docs/installation/google.md | 271 - docs/docs/installation/helm.md | 123 - docs/docs/installation/kubernetes.md | 173 - docs/docs/installation/overview.md | 259 - docs/docs/permify-overview/_category_.json | 5 - .../permify-overview/authorization-service.md | 80 - docs/docs/permify-overview/infrastructure.md | 79 - docs/docs/permify-overview/intro.md | 117 - docs/docs/playground.md | 160 - docs/docs/reference/_category_.json | 5 - docs/docs/reference/cache.md | 95 - docs/docs/reference/configuration.md | 564 - docs/docs/reference/contextual-tuples.md | 189 - docs/docs/reference/glossary.md | 45 - docs/docs/reference/snap-tokens.md | 59 - docs/docs/reference/tracing.md | 56 - docs/docs/use-cases.md | 20 - docs/docs/use-cases/_category_.json | 6 - docs/docs/use-cases/_list.json | 32 - docs/docs/use-cases/abac.md | 631 -- docs/docs/use-cases/custom-roles.md | 74 - docs/docs/use-cases/multi-tenancy.md | 154 - docs/docs/use-cases/rebac.md | 424 - docs/docs/use-cases/simple-rbac.md | 128 - docs/docusaurus.config.js | 154 - docs/package.json | 91 - docs/sidebars.js | 204 - docs/src/components/Card/Card.jsx | 31 - docs/src/components/Card/Card.module.css | 96 - docs/src/components/Card/CardList.jsx | 72 - docs/src/components/Card/index.jsx | 4 - docs/src/components/Case/Case.jsx | 27 - docs/src/components/Case/Case.module.css | 91 - docs/src/components/Case/CaseList.jsx | 13 - docs/src/components/Case/index.jsx | 4 - .../components/HomepageFeatures.module.css | 11 - docs/src/components/HomepageFeatures.tsx | 75 - docs/src/css/custom.css | 187 - docs/src/pages/index.js | 6 - docs/src/pages/index.module.css | 23 - docs/src/theme/prism-include-languages.js | 28 - docs/src/theme/prism-perm-lang.js | 14 - docs/static/.nojekyll | 0 docs/static/img/docusaurus.png | Bin 5142 -> 0 bytes docs/static/img/favicon.ico | Bin 18057 -> 0 bytes docs/static/img/logo.svg | 3 - .../img/tutorial/docsVersionDropdown.png | Bin 25102 -> 0 bytes docs/static/img/tutorial/localeDropdown.png | Bin 30020 -> 0 bytes .../static/img/undraw_docusaurus_mountain.svg | 170 - docs/static/img/undraw_docusaurus_react.svg | 169 - docs/static/img/undraw_docusaurus_tree.svg | 1 - docs/tsconfig.json | 7 - .../version-0.2.x/api-overview.md | 36 - .../api-overview/_category_.json | 6 - .../version-0.2.x/api-overview/check-api.md | 122 - .../api-overview/delete-relationships.md | 98 - .../version-0.2.x/api-overview/expand-api.md | 308 - .../version-0.2.x/api-overview/read-api.md | 131 - .../api-overview/schema-lookup.md | 90 - .../api-overview/write-relationships.md | 190 - .../api-overview/write-schema.md | 72 - .../version-0.2.x/deployment/_category_.json | 6 - .../version-0.2.x/deployment/aws.md | 188 - .../version-0.2.x/deployment/azure.md | 7 - .../version-0.2.x/deployment/google.md | 7 - .../version-0.2.x/deployment/kubernetes.md | 174 - .../example-use-cases/_category_.json | 6 - .../example-use-cases/organizational.md | 151 - .../example-use-cases/ownership.md | 42 - .../example-use-cases/parent-child.md | 141 - .../example-use-cases/sharing.md | 40 - .../example-use-cases/simple-rbac.md | 130 - .../example-use-cases/user-groups.md | 203 - .../getting-started/_category_.json | 5 - .../getting-started/enforcement.md | 239 - .../version-0.2.x/getting-started/modeling.md | 241 - .../getting-started/sync-data.md | 179 - .../version-0.2.x/getting-started/testing.md | 104 - .../installation/_category_.json | 5 - .../version-0.2.x/installation/brew.md | 69 - .../version-0.2.x/installation/container.md | 136 - .../version-0.2.x/installation/overview.md | 266 - .../permify-overview/_category_.json | 5 - .../permify-overview/authorization-service.md | 42 - .../permify-overview/infrastructure.md | 50 - .../version-0.2.x/permify-overview/intro.md | 101 - .../version-0.2.x/playground.md | 74 - .../version-0.2.x/reference/_category_.json | 5 - .../version-0.2.x/reference/glossary.md | 45 - .../version-0.2.x/reference/snap-tokens.md | 50 - .../version-0.2.x/reference/tracing.md | 52 - .../version-0.3.x/api-overview.md | 52 - .../api-overview/_category_.json | 6 - .../api-overview/permission/_category_.json | 6 - .../api-overview/permission/check-api.md | 130 - .../api-overview/permission/expand-api.md | 329 - .../api-overview/permission/lookup-entity.md | 178 - .../api-overview/permission/schema-lookup.md | 95 - .../api-overview/relationship/_category_.json | 6 - .../relationship/delete-relationships.md | 103 - .../api-overview/relationship/read-api.md | 103 - .../relationship/write-relationships.md | 196 - .../api-overview/schema/_category_.json | 6 - .../api-overview/schema/write-schema.md | 93 - .../api-overview/tenancy/_category_.json | 6 - .../api-overview/tenancy/create-tenant.md | 55 - .../api-overview/tenancy/delete-tenant.md | 44 - .../version-0.3.x/comparision.md | 38 - .../getting-started/_category_.json | 5 - .../getting-started/enforcement.md | 91 - .../getting-started/examples/_category_.json | 6 - .../examples/facebook-groups.md | 537 - .../getting-started/examples/google-docs.md | 304 - .../getting-started/examples/notion.md | 540 - .../version-0.3.x/getting-started/modeling.md | 287 - .../getting-started/sync-data.md | 445 - .../version-0.3.x/getting-started/testing.md | 106 - .../version-0.3.x/installation.md | 17 - .../installation/_category_.json | 5 - .../version-0.3.x/installation/aws.md | 187 - .../version-0.3.x/installation/azure.md | 7 - .../version-0.3.x/installation/brew.md | 52 - .../version-0.3.x/installation/container.md | 55 - .../version-0.3.x/installation/google.md | 7 - .../version-0.3.x/installation/kubernetes.md | 173 - .../version-0.3.x/installation/overview.md | 253 - .../versioned_docs/version-0.3.x/migrating.md | 153 - .../permify-overview/_category_.json | 5 - .../permify-overview/authorization-service.md | 42 - .../permify-overview/infrastructure.md | 50 - .../version-0.3.x/permify-overview/intro.md | 114 - .../version-0.3.x/playground.md | 74 - .../version-0.3.x/reference/_category_.json | 5 - .../version-0.3.x/reference/configuration.md | 312 - .../version-0.3.x/reference/glossary.md | 45 - .../version-0.3.x/reference/snap-tokens.md | 50 - .../version-0.3.x/reference/tracing.md | 52 - .../versioned_docs/version-0.3.x/use-cases.md | 20 - .../version-0.3.x/use-cases/_category_.json | 6 - .../version-0.3.x/use-cases/_list.json | 38 - .../use-cases/nested-hierarchies.md | 73 - .../version-0.3.x/use-cases/organizational.md | 149 - .../version-0.3.x/use-cases/ownership.md | 42 - .../version-0.3.x/use-cases/sharing.md | 40 - .../version-0.3.x/use-cases/simple-rbac.md | 128 - .../version-0.3.x/use-cases/user-groups.md | 201 - .../version-0.4.x/api-overview.md | 83 - .../api-overview/_category_.json | 6 - .../api-overview/permission/_category_.json | 6 - .../api-overview/permission/check-api.md | 192 - .../api-overview/permission/expand-api.md | 314 - .../api-overview/permission/lookup-entity.md | 216 - .../api-overview/permission/lookup-subject.md | 107 - .../permission/subject-permission.md | 128 - .../api-overview/relationship/_category_.json | 6 - .../relationship/delete-relationships.md | 103 - .../api-overview/relationship/read-api.md | 103 - .../relationship/write-relationships.md | 196 - .../api-overview/schema/_category_.json | 6 - .../api-overview/schema/write-schema.md | 93 - .../api-overview/tenancy/_category_.json | 6 - .../api-overview/tenancy/create-tenant.md | 55 - .../api-overview/tenancy/delete-tenant.md | 44 - .../api-overview/watch/_category_.json | 6 - .../api-overview/watch/watch-changes.md | 140 - .../version-0.4.x/comparision.md | 38 - .../getting-started/_category_.json | 5 - .../getting-started/enforcement.md | 93 - .../getting-started/examples/_category_.json | 6 - .../examples/facebook-groups.md | 546 - .../getting-started/examples/google-docs.md | 344 - .../getting-started/examples/notion.md | 548 - .../version-0.4.x/getting-started/modeling.md | 442 - .../getting-started/sync-data.md | 461 - .../version-0.4.x/getting-started/testing.md | 243 - .../version-0.4.x/installation.md | 17 - .../installation/_category_.json | 5 - .../version-0.4.x/installation/aws.md | 187 - .../version-0.4.x/installation/azure.md | 7 - .../version-0.4.x/installation/brew.md | 66 - .../version-0.4.x/installation/container.md | 58 - .../version-0.4.x/installation/google.md | 271 - .../version-0.4.x/installation/kubernetes.md | 173 - .../version-0.4.x/installation/overview.md | 259 - .../permify-overview/_category_.json | 5 - .../permify-overview/authorization-service.md | 42 - .../permify-overview/infrastructure.md | 50 - .../version-0.4.x/permify-overview/intro.md | 130 - .../version-0.4.x/playground.md | 136 - .../version-0.4.x/reference/_category_.json | 5 - .../version-0.4.x/reference/cache.md | 87 - .../version-0.4.x/reference/configuration.md | 465 - .../reference/contextual-tuples.md | 246 - .../version-0.4.x/reference/glossary.md | 45 - .../version-0.4.x/reference/snap-tokens.md | 54 - .../version-0.4.x/reference/tracing.md | 55 - .../versioned_docs/version-0.4.x/use-cases.md | 20 - .../version-0.4.x/use-cases/_category_.json | 6 - .../version-0.4.x/use-cases/_list.json | 32 - .../version-0.4.x/use-cases/abac.md | 590 -- .../version-0.4.x/use-cases/custom-roles.md | 74 - .../version-0.4.x/use-cases/multi-tenancy.md | 149 - .../use-cases/nested-hierarchies.md | 73 - .../version-0.4.x/use-cases/organizational.md | 149 - .../version-0.4.x/use-cases/ownership.md | 42 - .../version-0.4.x/use-cases/rebac.md | 426 - .../version-0.4.x/use-cases/sharing.md | 40 - .../version-0.4.x/use-cases/simple-rbac.md | 128 - .../version-0.4.x/use-cases/user-groups.md | 201 - .../version-0.5.x/api-overview.md | 83 - .../api-overview/_category_.json | 6 - .../api-overview/data/_category_.json | 6 - .../api-overview/data/delete-data.md | 108 - .../api-overview/data/read-attributes.md | 94 - .../api-overview/data/read-relationships.md | 105 - .../api-overview/data/write-data.md | 366 - .../api-overview/permission/_category_.json | 6 - .../api-overview/permission/check-api.md | 194 - .../api-overview/permission/expand-api.md | 316 - .../api-overview/permission/lookup-entity.md | 222 - .../api-overview/permission/lookup-subject.md | 109 - .../permission/subject-permission.md | 130 - .../api-overview/schema/_category_.json | 6 - .../api-overview/schema/write-schema.md | 95 - .../api-overview/tenancy/_category_.json | 6 - .../api-overview/tenancy/create-tenant.md | 57 - .../api-overview/tenancy/delete-tenant.md | 46 - .../api-overview/watch/_category_.json | 6 - .../api-overview/watch/watch-changes.md | 142 - .../version-0.5.x/comparision.md | 38 - .../getting-started/_category_.json | 5 - .../getting-started/enforcement.md | 80 - .../getting-started/examples/_category_.json | 6 - .../examples/facebook-groups.md | 546 - .../getting-started/examples/google-docs.md | 344 - .../getting-started/examples/instagram.md | 376 - .../getting-started/examples/mercury.md | 74 - .../getting-started/examples/notion.md | 548 - .../version-0.5.x/getting-started/modeling.md | 555 - .../getting-started/sync-data.md | 456 - .../version-0.5.x/getting-started/testing.md | 279 - .../version-0.5.x/installation.md | 17 - .../installation/_category_.json | 5 - .../version-0.5.x/installation/aws.md | 187 - .../version-0.5.x/installation/azure.md | 7 - .../version-0.5.x/installation/brew.md | 66 - .../version-0.5.x/installation/container.md | 55 - .../version-0.5.x/installation/google.md | 271 - .../version-0.5.x/installation/kubernetes.md | 173 - .../version-0.5.x/installation/overview.md | 259 - .../permify-overview/_category_.json | 5 - .../permify-overview/authorization-service.md | 40 - .../permify-overview/infrastructure.md | 79 - .../version-0.5.x/permify-overview/intro.md | 117 - .../version-0.5.x/playground.md | 160 - .../version-0.5.x/reference/_category_.json | 5 - .../version-0.5.x/reference/cache.md | 95 - .../version-0.5.x/reference/configuration.md | 491 - .../reference/contextual-tuples.md | 252 - .../version-0.5.x/reference/glossary.md | 45 - .../version-0.5.x/reference/snap-tokens.md | 53 - .../version-0.5.x/reference/tracing.md | 55 - .../versioned_docs/version-0.5.x/use-cases.md | 20 - .../version-0.5.x/use-cases/_category_.json | 6 - .../version-0.5.x/use-cases/_list.json | 32 - .../version-0.5.x/use-cases/abac.md | 590 -- .../version-0.5.x/use-cases/custom-roles.md | 74 - .../version-0.5.x/use-cases/multi-tenancy.md | 154 - .../version-0.5.x/use-cases/rebac.md | 424 - .../version-0.5.x/use-cases/simple-rbac.md | 128 - .../version-0.6.x/api-overview.md | 93 - .../api-overview/_category_.json | 6 - .../api-overview/bundle/_category_.json | 5 - .../api-overview/bundle/delete-bundle.md | 58 - .../api-overview/bundle/read-bundle.md | 58 - .../api-overview/bundle/write-bundle.md | 117 - .../api-overview/data/_category_.json | 5 - .../api-overview/data/delete-data.md | 111 - .../api-overview/data/read-attributes.md | 95 - .../api-overview/data/read-relationships.md | 106 - .../api-overview/data/run-bundle.md | 75 - .../api-overview/data/write-data.md | 477 - .../api-overview/permission/_category_.json | 5 - .../api-overview/permission/check-api.md | 197 - .../api-overview/permission/expand-api.md | 319 - .../api-overview/permission/lookup-entity.md | 228 - .../api-overview/permission/lookup-subject.md | 116 - .../permission/subject-permission.md | 133 - .../api-overview/schema/_category_.json | 6 - .../api-overview/schema/write-schema.md | 97 - .../api-overview/tenancy/_category_.json | 5 - .../api-overview/tenancy/create-tenant.md | 59 - .../api-overview/tenancy/delete-tenant.md | 47 - .../api-overview/watch/_category_.json | 5 - .../api-overview/watch/watch-changes.md | 145 - docs/versioned_docs/version-0.6.x/bundle.md | 84 - .../version-0.6.x/comparision.md | 38 - docs/versioned_docs/version-0.6.x/examples.md | 18 - .../getting-started/_category_.json | 5 - .../getting-started/enforcement.md | 80 - .../getting-started/examples/_category_.json | 6 - .../examples/facebook-groups.md | 546 - .../getting-started/examples/google-docs.md | 344 - .../getting-started/examples/instagram.md | 376 - .../getting-started/examples/mercury.md | 157 - .../getting-started/examples/notion.md | 548 - .../version-0.6.x/getting-started/modeling.md | 608 -- .../getting-started/sync-data.md | 480 - .../version-0.6.x/getting-started/testing.md | 279 - .../version-0.6.x/installation.md | 17 - .../installation/_category_.json | 5 - .../version-0.6.x/installation/aws.md | 187 - .../version-0.6.x/installation/azure.md | 7 - .../version-0.6.x/installation/brew.md | 66 - .../version-0.6.x/installation/container.md | 55 - .../version-0.6.x/installation/google.md | 271 - .../version-0.6.x/installation/kubernetes.md | 173 - .../version-0.6.x/installation/overview.md | 259 - .../permify-overview/_category_.json | 5 - .../permify-overview/authorization-service.md | 40 - .../permify-overview/infrastructure.md | 79 - .../version-0.6.x/permify-overview/intro.md | 117 - .../version-0.6.x/playground.md | 160 - .../version-0.6.x/reference/_category_.json | 5 - .../version-0.6.x/reference/cache.md | 95 - .../version-0.6.x/reference/configuration.md | 553 - .../reference/contextual-tuples.md | 189 - .../version-0.6.x/reference/glossary.md | 45 - .../version-0.6.x/reference/snap-tokens.md | 59 - .../version-0.6.x/reference/tracing.md | 56 - .../versioned_docs/version-0.6.x/use-cases.md | 20 - .../version-0.6.x/use-cases/_category_.json | 6 - .../version-0.6.x/use-cases/_list.json | 32 - .../version-0.6.x/use-cases/abac.md | 631 -- .../version-0.6.x/use-cases/custom-roles.md | 74 - .../version-0.6.x/use-cases/multi-tenancy.md | 154 - .../version-0.6.x/use-cases/rebac.md | 424 - .../version-0.6.x/use-cases/simple-rbac.md | 128 - .../version-0.2.x-sidebars.json | 122 - .../version-0.3.x-sidebars.json | 175 - .../version-0.4.x-sidebars.json | 184 - .../version-0.5.x-sidebars.json | 187 - .../version-0.6.x-sidebars.json | 200 - docs/versions.json | 7 - docs/yarn.lock | 9157 ----------------- 400 files changed, 63505 deletions(-) create mode 100644 docs/.DS_Store delete mode 100644 docs/.gitignore delete mode 100644 docs/LICENSE delete mode 100644 docs/README.md delete mode 100644 docs/apidocs.swagger.json delete mode 100644 docs/babel.config.js delete mode 100644 docs/client-development-en/0.pack delete mode 100644 docs/client-development-en/index.pack delete mode 100644 docs/docs/api-overview.md delete mode 100644 docs/docs/api-overview/_category_.json delete mode 100644 docs/docs/api-overview/bundle/_category_.json delete mode 100644 docs/docs/api-overview/bundle/delete-bundle.md delete mode 100644 docs/docs/api-overview/bundle/read-bundle.md delete mode 100644 docs/docs/api-overview/bundle/write-bundle.md delete mode 100644 docs/docs/api-overview/data/_category_.json delete mode 100644 docs/docs/api-overview/data/delete-data.md delete mode 100644 docs/docs/api-overview/data/read-attributes.md delete mode 100644 docs/docs/api-overview/data/read-relationships.md delete mode 100644 docs/docs/api-overview/data/run-bundle.md delete mode 100644 docs/docs/api-overview/data/write-data.md delete mode 100644 docs/docs/api-overview/permission/_category_.json delete mode 100644 docs/docs/api-overview/permission/check-api.md delete mode 100644 docs/docs/api-overview/permission/expand-api.md delete mode 100644 docs/docs/api-overview/permission/lookup-entity.md delete mode 100644 docs/docs/api-overview/permission/lookup-subject.md delete mode 100644 docs/docs/api-overview/permission/subject-permission.md delete mode 100644 docs/docs/api-overview/schema/_category_.json delete mode 100644 docs/docs/api-overview/schema/list-schema.md delete mode 100644 docs/docs/api-overview/schema/read-schema.md delete mode 100644 docs/docs/api-overview/schema/write-schema.md delete mode 100644 docs/docs/api-overview/tenancy/_category_.json delete mode 100644 docs/docs/api-overview/tenancy/create-tenant.md delete mode 100644 docs/docs/api-overview/tenancy/delete-tenant.md delete mode 100644 docs/docs/api-overview/watch/_category_.json delete mode 100644 docs/docs/api-overview/watch/watch-changes.md delete mode 100644 docs/docs/bundle.md delete mode 100644 docs/docs/comparision.md delete mode 100644 docs/docs/examples.md delete mode 100644 docs/docs/getting-started/_category_.json delete mode 100644 docs/docs/getting-started/enforcement.md delete mode 100644 docs/docs/getting-started/examples/_category_.json delete mode 100644 docs/docs/getting-started/examples/facebook-groups.md delete mode 100644 docs/docs/getting-started/examples/google-docs.md delete mode 100644 docs/docs/getting-started/examples/instagram.md delete mode 100644 docs/docs/getting-started/examples/mercury.md delete mode 100644 docs/docs/getting-started/examples/notion.md delete mode 100644 docs/docs/getting-started/modeling.md delete mode 100644 docs/docs/getting-started/sync-data.md delete mode 100644 docs/docs/getting-started/testing.md delete mode 100644 docs/docs/installation.md delete mode 100644 docs/docs/installation/_category_.json delete mode 100644 docs/docs/installation/aws.md delete mode 100644 docs/docs/installation/azure.md delete mode 100644 docs/docs/installation/brew.md delete mode 100644 docs/docs/installation/container.md delete mode 100644 docs/docs/installation/google.md delete mode 100644 docs/docs/installation/helm.md delete mode 100644 docs/docs/installation/kubernetes.md delete mode 100644 docs/docs/installation/overview.md delete mode 100644 docs/docs/permify-overview/_category_.json delete mode 100644 docs/docs/permify-overview/authorization-service.md delete mode 100644 docs/docs/permify-overview/infrastructure.md delete mode 100644 docs/docs/permify-overview/intro.md delete mode 100644 docs/docs/playground.md delete mode 100644 docs/docs/reference/_category_.json delete mode 100644 docs/docs/reference/cache.md delete mode 100644 docs/docs/reference/configuration.md delete mode 100644 docs/docs/reference/contextual-tuples.md delete mode 100644 docs/docs/reference/glossary.md delete mode 100644 docs/docs/reference/snap-tokens.md delete mode 100644 docs/docs/reference/tracing.md delete mode 100644 docs/docs/use-cases.md delete mode 100644 docs/docs/use-cases/_category_.json delete mode 100644 docs/docs/use-cases/_list.json delete mode 100644 docs/docs/use-cases/abac.md delete mode 100644 docs/docs/use-cases/custom-roles.md delete mode 100644 docs/docs/use-cases/multi-tenancy.md delete mode 100644 docs/docs/use-cases/rebac.md delete mode 100644 docs/docs/use-cases/simple-rbac.md delete mode 100644 docs/docusaurus.config.js delete mode 100644 docs/package.json delete mode 100644 docs/sidebars.js delete mode 100644 docs/src/components/Card/Card.jsx delete mode 100644 docs/src/components/Card/Card.module.css delete mode 100644 docs/src/components/Card/CardList.jsx delete mode 100644 docs/src/components/Card/index.jsx delete mode 100644 docs/src/components/Case/Case.jsx delete mode 100644 docs/src/components/Case/Case.module.css delete mode 100644 docs/src/components/Case/CaseList.jsx delete mode 100644 docs/src/components/Case/index.jsx delete mode 100644 docs/src/components/HomepageFeatures.module.css delete mode 100644 docs/src/components/HomepageFeatures.tsx delete mode 100644 docs/src/css/custom.css delete mode 100644 docs/src/pages/index.js delete mode 100644 docs/src/pages/index.module.css delete mode 100644 docs/src/theme/prism-include-languages.js delete mode 100644 docs/src/theme/prism-perm-lang.js delete mode 100644 docs/static/.nojekyll delete mode 100644 docs/static/img/docusaurus.png delete mode 100644 docs/static/img/favicon.ico delete mode 100644 docs/static/img/logo.svg delete mode 100644 docs/static/img/tutorial/docsVersionDropdown.png delete mode 100644 docs/static/img/tutorial/localeDropdown.png delete mode 100644 docs/static/img/undraw_docusaurus_mountain.svg delete mode 100644 docs/static/img/undraw_docusaurus_react.svg delete mode 100644 docs/static/img/undraw_docusaurus_tree.svg delete mode 100644 docs/tsconfig.json delete mode 100644 docs/versioned_docs/version-0.2.x/api-overview.md delete mode 100644 docs/versioned_docs/version-0.2.x/api-overview/_category_.json delete mode 100644 docs/versioned_docs/version-0.2.x/api-overview/check-api.md delete mode 100644 docs/versioned_docs/version-0.2.x/api-overview/delete-relationships.md delete mode 100644 docs/versioned_docs/version-0.2.x/api-overview/expand-api.md delete mode 100644 docs/versioned_docs/version-0.2.x/api-overview/read-api.md delete mode 100644 docs/versioned_docs/version-0.2.x/api-overview/schema-lookup.md delete mode 100644 docs/versioned_docs/version-0.2.x/api-overview/write-relationships.md delete mode 100644 docs/versioned_docs/version-0.2.x/api-overview/write-schema.md delete mode 100644 docs/versioned_docs/version-0.2.x/deployment/_category_.json delete mode 100644 docs/versioned_docs/version-0.2.x/deployment/aws.md delete mode 100644 docs/versioned_docs/version-0.2.x/deployment/azure.md delete mode 100644 docs/versioned_docs/version-0.2.x/deployment/google.md delete mode 100644 docs/versioned_docs/version-0.2.x/deployment/kubernetes.md delete mode 100644 docs/versioned_docs/version-0.2.x/example-use-cases/_category_.json delete mode 100644 docs/versioned_docs/version-0.2.x/example-use-cases/organizational.md delete mode 100644 docs/versioned_docs/version-0.2.x/example-use-cases/ownership.md delete mode 100644 docs/versioned_docs/version-0.2.x/example-use-cases/parent-child.md delete mode 100644 docs/versioned_docs/version-0.2.x/example-use-cases/sharing.md delete mode 100644 docs/versioned_docs/version-0.2.x/example-use-cases/simple-rbac.md delete mode 100644 docs/versioned_docs/version-0.2.x/example-use-cases/user-groups.md delete mode 100644 docs/versioned_docs/version-0.2.x/getting-started/_category_.json delete mode 100644 docs/versioned_docs/version-0.2.x/getting-started/enforcement.md delete mode 100644 docs/versioned_docs/version-0.2.x/getting-started/modeling.md delete mode 100644 docs/versioned_docs/version-0.2.x/getting-started/sync-data.md delete mode 100644 docs/versioned_docs/version-0.2.x/getting-started/testing.md delete mode 100644 docs/versioned_docs/version-0.2.x/installation/_category_.json delete mode 100644 docs/versioned_docs/version-0.2.x/installation/brew.md delete mode 100644 docs/versioned_docs/version-0.2.x/installation/container.md delete mode 100644 docs/versioned_docs/version-0.2.x/installation/overview.md delete mode 100644 docs/versioned_docs/version-0.2.x/permify-overview/_category_.json delete mode 100644 docs/versioned_docs/version-0.2.x/permify-overview/authorization-service.md delete mode 100644 docs/versioned_docs/version-0.2.x/permify-overview/infrastructure.md delete mode 100644 docs/versioned_docs/version-0.2.x/permify-overview/intro.md delete mode 100644 docs/versioned_docs/version-0.2.x/playground.md delete mode 100644 docs/versioned_docs/version-0.2.x/reference/_category_.json delete mode 100644 docs/versioned_docs/version-0.2.x/reference/glossary.md delete mode 100644 docs/versioned_docs/version-0.2.x/reference/snap-tokens.md delete mode 100644 docs/versioned_docs/version-0.2.x/reference/tracing.md delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview.md delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview/_category_.json delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview/permission/_category_.json delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview/permission/check-api.md delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview/permission/expand-api.md delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview/permission/lookup-entity.md delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview/permission/schema-lookup.md delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview/relationship/_category_.json delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview/relationship/delete-relationships.md delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview/relationship/read-api.md delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview/relationship/write-relationships.md delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview/schema/_category_.json delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview/schema/write-schema.md delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview/tenancy/_category_.json delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview/tenancy/create-tenant.md delete mode 100644 docs/versioned_docs/version-0.3.x/api-overview/tenancy/delete-tenant.md delete mode 100644 docs/versioned_docs/version-0.3.x/comparision.md delete mode 100644 docs/versioned_docs/version-0.3.x/getting-started/_category_.json delete mode 100644 docs/versioned_docs/version-0.3.x/getting-started/enforcement.md delete mode 100644 docs/versioned_docs/version-0.3.x/getting-started/examples/_category_.json delete mode 100644 docs/versioned_docs/version-0.3.x/getting-started/examples/facebook-groups.md delete mode 100644 docs/versioned_docs/version-0.3.x/getting-started/examples/google-docs.md delete mode 100644 docs/versioned_docs/version-0.3.x/getting-started/examples/notion.md delete mode 100644 docs/versioned_docs/version-0.3.x/getting-started/modeling.md delete mode 100644 docs/versioned_docs/version-0.3.x/getting-started/sync-data.md delete mode 100644 docs/versioned_docs/version-0.3.x/getting-started/testing.md delete mode 100644 docs/versioned_docs/version-0.3.x/installation.md delete mode 100644 docs/versioned_docs/version-0.3.x/installation/_category_.json delete mode 100644 docs/versioned_docs/version-0.3.x/installation/aws.md delete mode 100644 docs/versioned_docs/version-0.3.x/installation/azure.md delete mode 100644 docs/versioned_docs/version-0.3.x/installation/brew.md delete mode 100644 docs/versioned_docs/version-0.3.x/installation/container.md delete mode 100644 docs/versioned_docs/version-0.3.x/installation/google.md delete mode 100644 docs/versioned_docs/version-0.3.x/installation/kubernetes.md delete mode 100644 docs/versioned_docs/version-0.3.x/installation/overview.md delete mode 100644 docs/versioned_docs/version-0.3.x/migrating.md delete mode 100644 docs/versioned_docs/version-0.3.x/permify-overview/_category_.json delete mode 100644 docs/versioned_docs/version-0.3.x/permify-overview/authorization-service.md delete mode 100644 docs/versioned_docs/version-0.3.x/permify-overview/infrastructure.md delete mode 100644 docs/versioned_docs/version-0.3.x/permify-overview/intro.md delete mode 100644 docs/versioned_docs/version-0.3.x/playground.md delete mode 100644 docs/versioned_docs/version-0.3.x/reference/_category_.json delete mode 100644 docs/versioned_docs/version-0.3.x/reference/configuration.md delete mode 100644 docs/versioned_docs/version-0.3.x/reference/glossary.md delete mode 100644 docs/versioned_docs/version-0.3.x/reference/snap-tokens.md delete mode 100644 docs/versioned_docs/version-0.3.x/reference/tracing.md delete mode 100644 docs/versioned_docs/version-0.3.x/use-cases.md delete mode 100644 docs/versioned_docs/version-0.3.x/use-cases/_category_.json delete mode 100644 docs/versioned_docs/version-0.3.x/use-cases/_list.json delete mode 100644 docs/versioned_docs/version-0.3.x/use-cases/nested-hierarchies.md delete mode 100644 docs/versioned_docs/version-0.3.x/use-cases/organizational.md delete mode 100644 docs/versioned_docs/version-0.3.x/use-cases/ownership.md delete mode 100644 docs/versioned_docs/version-0.3.x/use-cases/sharing.md delete mode 100644 docs/versioned_docs/version-0.3.x/use-cases/simple-rbac.md delete mode 100644 docs/versioned_docs/version-0.3.x/use-cases/user-groups.md delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview.md delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/_category_.json delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/permission/_category_.json delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/permission/check-api.md delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/permission/expand-api.md delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/permission/lookup-entity.md delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/permission/lookup-subject.md delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/permission/subject-permission.md delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/relationship/_category_.json delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/relationship/delete-relationships.md delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/relationship/read-api.md delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/relationship/write-relationships.md delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/schema/_category_.json delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/schema/write-schema.md delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/tenancy/_category_.json delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/tenancy/create-tenant.md delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/tenancy/delete-tenant.md delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/watch/_category_.json delete mode 100644 docs/versioned_docs/version-0.4.x/api-overview/watch/watch-changes.md delete mode 100644 docs/versioned_docs/version-0.4.x/comparision.md delete mode 100644 docs/versioned_docs/version-0.4.x/getting-started/_category_.json delete mode 100644 docs/versioned_docs/version-0.4.x/getting-started/enforcement.md delete mode 100644 docs/versioned_docs/version-0.4.x/getting-started/examples/_category_.json delete mode 100644 docs/versioned_docs/version-0.4.x/getting-started/examples/facebook-groups.md delete mode 100644 docs/versioned_docs/version-0.4.x/getting-started/examples/google-docs.md delete mode 100644 docs/versioned_docs/version-0.4.x/getting-started/examples/notion.md delete mode 100644 docs/versioned_docs/version-0.4.x/getting-started/modeling.md delete mode 100644 docs/versioned_docs/version-0.4.x/getting-started/sync-data.md delete mode 100644 docs/versioned_docs/version-0.4.x/getting-started/testing.md delete mode 100644 docs/versioned_docs/version-0.4.x/installation.md delete mode 100644 docs/versioned_docs/version-0.4.x/installation/_category_.json delete mode 100644 docs/versioned_docs/version-0.4.x/installation/aws.md delete mode 100644 docs/versioned_docs/version-0.4.x/installation/azure.md delete mode 100644 docs/versioned_docs/version-0.4.x/installation/brew.md delete mode 100644 docs/versioned_docs/version-0.4.x/installation/container.md delete mode 100644 docs/versioned_docs/version-0.4.x/installation/google.md delete mode 100644 docs/versioned_docs/version-0.4.x/installation/kubernetes.md delete mode 100644 docs/versioned_docs/version-0.4.x/installation/overview.md delete mode 100644 docs/versioned_docs/version-0.4.x/permify-overview/_category_.json delete mode 100644 docs/versioned_docs/version-0.4.x/permify-overview/authorization-service.md delete mode 100644 docs/versioned_docs/version-0.4.x/permify-overview/infrastructure.md delete mode 100644 docs/versioned_docs/version-0.4.x/permify-overview/intro.md delete mode 100644 docs/versioned_docs/version-0.4.x/playground.md delete mode 100644 docs/versioned_docs/version-0.4.x/reference/_category_.json delete mode 100644 docs/versioned_docs/version-0.4.x/reference/cache.md delete mode 100644 docs/versioned_docs/version-0.4.x/reference/configuration.md delete mode 100644 docs/versioned_docs/version-0.4.x/reference/contextual-tuples.md delete mode 100644 docs/versioned_docs/version-0.4.x/reference/glossary.md delete mode 100644 docs/versioned_docs/version-0.4.x/reference/snap-tokens.md delete mode 100644 docs/versioned_docs/version-0.4.x/reference/tracing.md delete mode 100644 docs/versioned_docs/version-0.4.x/use-cases.md delete mode 100644 docs/versioned_docs/version-0.4.x/use-cases/_category_.json delete mode 100644 docs/versioned_docs/version-0.4.x/use-cases/_list.json delete mode 100644 docs/versioned_docs/version-0.4.x/use-cases/abac.md delete mode 100644 docs/versioned_docs/version-0.4.x/use-cases/custom-roles.md delete mode 100644 docs/versioned_docs/version-0.4.x/use-cases/multi-tenancy.md delete mode 100644 docs/versioned_docs/version-0.4.x/use-cases/nested-hierarchies.md delete mode 100644 docs/versioned_docs/version-0.4.x/use-cases/organizational.md delete mode 100644 docs/versioned_docs/version-0.4.x/use-cases/ownership.md delete mode 100644 docs/versioned_docs/version-0.4.x/use-cases/rebac.md delete mode 100644 docs/versioned_docs/version-0.4.x/use-cases/sharing.md delete mode 100644 docs/versioned_docs/version-0.4.x/use-cases/simple-rbac.md delete mode 100644 docs/versioned_docs/version-0.4.x/use-cases/user-groups.md delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview.md delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/_category_.json delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/data/_category_.json delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/data/delete-data.md delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/data/read-attributes.md delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/data/read-relationships.md delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/data/write-data.md delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/permission/_category_.json delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/permission/check-api.md delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/permission/expand-api.md delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/permission/lookup-entity.md delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/permission/lookup-subject.md delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/permission/subject-permission.md delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/schema/_category_.json delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/schema/write-schema.md delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/tenancy/_category_.json delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/tenancy/create-tenant.md delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/tenancy/delete-tenant.md delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/watch/_category_.json delete mode 100644 docs/versioned_docs/version-0.5.x/api-overview/watch/watch-changes.md delete mode 100644 docs/versioned_docs/version-0.5.x/comparision.md delete mode 100644 docs/versioned_docs/version-0.5.x/getting-started/_category_.json delete mode 100644 docs/versioned_docs/version-0.5.x/getting-started/enforcement.md delete mode 100644 docs/versioned_docs/version-0.5.x/getting-started/examples/_category_.json delete mode 100644 docs/versioned_docs/version-0.5.x/getting-started/examples/facebook-groups.md delete mode 100644 docs/versioned_docs/version-0.5.x/getting-started/examples/google-docs.md delete mode 100644 docs/versioned_docs/version-0.5.x/getting-started/examples/instagram.md delete mode 100644 docs/versioned_docs/version-0.5.x/getting-started/examples/mercury.md delete mode 100644 docs/versioned_docs/version-0.5.x/getting-started/examples/notion.md delete mode 100644 docs/versioned_docs/version-0.5.x/getting-started/modeling.md delete mode 100644 docs/versioned_docs/version-0.5.x/getting-started/sync-data.md delete mode 100644 docs/versioned_docs/version-0.5.x/getting-started/testing.md delete mode 100644 docs/versioned_docs/version-0.5.x/installation.md delete mode 100644 docs/versioned_docs/version-0.5.x/installation/_category_.json delete mode 100644 docs/versioned_docs/version-0.5.x/installation/aws.md delete mode 100644 docs/versioned_docs/version-0.5.x/installation/azure.md delete mode 100644 docs/versioned_docs/version-0.5.x/installation/brew.md delete mode 100644 docs/versioned_docs/version-0.5.x/installation/container.md delete mode 100644 docs/versioned_docs/version-0.5.x/installation/google.md delete mode 100644 docs/versioned_docs/version-0.5.x/installation/kubernetes.md delete mode 100644 docs/versioned_docs/version-0.5.x/installation/overview.md delete mode 100644 docs/versioned_docs/version-0.5.x/permify-overview/_category_.json delete mode 100644 docs/versioned_docs/version-0.5.x/permify-overview/authorization-service.md delete mode 100644 docs/versioned_docs/version-0.5.x/permify-overview/infrastructure.md delete mode 100644 docs/versioned_docs/version-0.5.x/permify-overview/intro.md delete mode 100644 docs/versioned_docs/version-0.5.x/playground.md delete mode 100644 docs/versioned_docs/version-0.5.x/reference/_category_.json delete mode 100644 docs/versioned_docs/version-0.5.x/reference/cache.md delete mode 100644 docs/versioned_docs/version-0.5.x/reference/configuration.md delete mode 100644 docs/versioned_docs/version-0.5.x/reference/contextual-tuples.md delete mode 100644 docs/versioned_docs/version-0.5.x/reference/glossary.md delete mode 100644 docs/versioned_docs/version-0.5.x/reference/snap-tokens.md delete mode 100644 docs/versioned_docs/version-0.5.x/reference/tracing.md delete mode 100644 docs/versioned_docs/version-0.5.x/use-cases.md delete mode 100644 docs/versioned_docs/version-0.5.x/use-cases/_category_.json delete mode 100644 docs/versioned_docs/version-0.5.x/use-cases/_list.json delete mode 100644 docs/versioned_docs/version-0.5.x/use-cases/abac.md delete mode 100644 docs/versioned_docs/version-0.5.x/use-cases/custom-roles.md delete mode 100644 docs/versioned_docs/version-0.5.x/use-cases/multi-tenancy.md delete mode 100644 docs/versioned_docs/version-0.5.x/use-cases/rebac.md delete mode 100644 docs/versioned_docs/version-0.5.x/use-cases/simple-rbac.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/_category_.json delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/bundle/_category_.json delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/bundle/delete-bundle.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/bundle/read-bundle.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/bundle/write-bundle.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/data/_category_.json delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/data/delete-data.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/data/read-attributes.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/data/read-relationships.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/data/run-bundle.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/data/write-data.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/permission/_category_.json delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/permission/check-api.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/permission/expand-api.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/permission/lookup-entity.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/permission/lookup-subject.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/permission/subject-permission.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/schema/_category_.json delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/schema/write-schema.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/tenancy/_category_.json delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/tenancy/create-tenant.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/tenancy/delete-tenant.md delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/watch/_category_.json delete mode 100644 docs/versioned_docs/version-0.6.x/api-overview/watch/watch-changes.md delete mode 100644 docs/versioned_docs/version-0.6.x/bundle.md delete mode 100644 docs/versioned_docs/version-0.6.x/comparision.md delete mode 100644 docs/versioned_docs/version-0.6.x/examples.md delete mode 100644 docs/versioned_docs/version-0.6.x/getting-started/_category_.json delete mode 100644 docs/versioned_docs/version-0.6.x/getting-started/enforcement.md delete mode 100644 docs/versioned_docs/version-0.6.x/getting-started/examples/_category_.json delete mode 100644 docs/versioned_docs/version-0.6.x/getting-started/examples/facebook-groups.md delete mode 100644 docs/versioned_docs/version-0.6.x/getting-started/examples/google-docs.md delete mode 100644 docs/versioned_docs/version-0.6.x/getting-started/examples/instagram.md delete mode 100644 docs/versioned_docs/version-0.6.x/getting-started/examples/mercury.md delete mode 100644 docs/versioned_docs/version-0.6.x/getting-started/examples/notion.md delete mode 100644 docs/versioned_docs/version-0.6.x/getting-started/modeling.md delete mode 100644 docs/versioned_docs/version-0.6.x/getting-started/sync-data.md delete mode 100644 docs/versioned_docs/version-0.6.x/getting-started/testing.md delete mode 100644 docs/versioned_docs/version-0.6.x/installation.md delete mode 100644 docs/versioned_docs/version-0.6.x/installation/_category_.json delete mode 100644 docs/versioned_docs/version-0.6.x/installation/aws.md delete mode 100644 docs/versioned_docs/version-0.6.x/installation/azure.md delete mode 100644 docs/versioned_docs/version-0.6.x/installation/brew.md delete mode 100644 docs/versioned_docs/version-0.6.x/installation/container.md delete mode 100644 docs/versioned_docs/version-0.6.x/installation/google.md delete mode 100644 docs/versioned_docs/version-0.6.x/installation/kubernetes.md delete mode 100644 docs/versioned_docs/version-0.6.x/installation/overview.md delete mode 100644 docs/versioned_docs/version-0.6.x/permify-overview/_category_.json delete mode 100644 docs/versioned_docs/version-0.6.x/permify-overview/authorization-service.md delete mode 100644 docs/versioned_docs/version-0.6.x/permify-overview/infrastructure.md delete mode 100644 docs/versioned_docs/version-0.6.x/permify-overview/intro.md delete mode 100644 docs/versioned_docs/version-0.6.x/playground.md delete mode 100644 docs/versioned_docs/version-0.6.x/reference/_category_.json delete mode 100644 docs/versioned_docs/version-0.6.x/reference/cache.md delete mode 100644 docs/versioned_docs/version-0.6.x/reference/configuration.md delete mode 100644 docs/versioned_docs/version-0.6.x/reference/contextual-tuples.md delete mode 100644 docs/versioned_docs/version-0.6.x/reference/glossary.md delete mode 100644 docs/versioned_docs/version-0.6.x/reference/snap-tokens.md delete mode 100644 docs/versioned_docs/version-0.6.x/reference/tracing.md delete mode 100644 docs/versioned_docs/version-0.6.x/use-cases.md delete mode 100644 docs/versioned_docs/version-0.6.x/use-cases/_category_.json delete mode 100644 docs/versioned_docs/version-0.6.x/use-cases/_list.json delete mode 100644 docs/versioned_docs/version-0.6.x/use-cases/abac.md delete mode 100644 docs/versioned_docs/version-0.6.x/use-cases/custom-roles.md delete mode 100644 docs/versioned_docs/version-0.6.x/use-cases/multi-tenancy.md delete mode 100644 docs/versioned_docs/version-0.6.x/use-cases/rebac.md delete mode 100644 docs/versioned_docs/version-0.6.x/use-cases/simple-rbac.md delete mode 100644 docs/versioned_sidebars/version-0.2.x-sidebars.json delete mode 100644 docs/versioned_sidebars/version-0.3.x-sidebars.json delete mode 100644 docs/versioned_sidebars/version-0.4.x-sidebars.json delete mode 100644 docs/versioned_sidebars/version-0.5.x-sidebars.json delete mode 100644 docs/versioned_sidebars/version-0.6.x-sidebars.json delete mode 100644 docs/versions.json delete mode 100644 docs/yarn.lock diff --git a/docs/.DS_Store b/docs/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..74e6c6d5d2180f79466cdf141ee0b6c4c3ff017b GIT binary patch literal 6148 zcmeH~JqiLr422VaK(Mj2oW=uqgF)64cmdIEgar$+pQHQogWzf{A}^496N7DlGn&*da{xyT@We>)GS>viy|N!D5o9KZ2;p2rjtAOR8}0TLjA zA0lA)Hf*sBWh4O-Ab}?Vdp{JoX-#dR{^~&R5db@un zGXgs64&4q9m3Qmg<5~WgSz9+a)XNcGJ_4|@t9T7}!+Eg - Permify Licence  - Permify Discord Channel  -

\ No newline at end of file diff --git a/docs/apidocs.swagger.json b/docs/apidocs.swagger.json deleted file mode 100644 index 2a90bb56a..000000000 --- a/docs/apidocs.swagger.json +++ /dev/null @@ -1,3329 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "title": "Permify API", - "description": "Permify is an open source authorization service for creating fine-grained and scalable authorization systems.", - "version": "v0.7.7", - "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" - } - }, - "tags": [ - { - "name": "Permission" - }, - { - "name": "Watch" - }, - { - "name": "Schema" - }, - { - "name": "Data" - }, - { - "name": "Bundle" - }, - { - "name": "Tenancy" - } - ], - "schemes": [ - "https" - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "paths": { - "/v1/tenants/create": { - "post": { - "summary": "create tenant", - "operationId": "tenants.create", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/TenantCreateResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "body", - "description": "TenantCreateRequest is the message used for the request to create a tenant.", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/TenantCreateRequest" - } - } - ], - "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}'" - } - ] - } - }, - "/v1/tenants/list": { - "post": { - "summary": "list tenants", - "operationId": "tenants.list", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/TenantListResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "body", - "description": "TenantListRequest is the message used for the request to list all tenants.", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/TenantListRequest" - } - } - ], - "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}'" - } - ] - } - }, - "/v1/tenants/{id}": { - "delete": { - "summary": "delete tenant", - "operationId": "tenants.delete", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/TenantDeleteResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "id", - "description": "id is the unique identifier of the tenant to be deleted.", - "in": "path", - "required": true, - "type": "string" - } - ], - "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'" - } - ] - } - }, - "/v1/tenants/{tenant_id}/bundle/delete": { - "post": { - "summary": "delete bundle", - "operationId": "bundle.delete", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/BundleDeleteResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "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." - } - } - ], - "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}'" - } - ] - } - }, - "/v1/tenants/{tenant_id}/bundle/read": { - "post": { - "summary": "read bundle", - "operationId": "bundle.read", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/BundleReadResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string" - } - } - } - } - ], - "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}'" - } - ] - } - }, - "/v1/tenants/{tenant_id}/bundle/write": { - "post": { - "summary": "write bundle", - "operationId": "bundle.write", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/BundleWriteResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "bundles": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/DataBundle" - }, - "description": "Contains the bundle data to be written." - } - }, - "description": "BundleWriteRequest is used to request the writing of a bundle.\nIt contains the tenant_id to identify the tenant and the Bundles object." - } - } - ], - "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 attributes", - "operationId": "data.attributes.read", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/AttributeReadResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "description": "tenant_id represents the unique identifier of the tenant from which the attributes are being read.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "metadata": { - "$ref": "#/definitions/AttributeReadRequestMetadata", - "description": "metadata holds additional information related to the request." - }, - "filter": { - "$ref": "#/definitions/AttributeFilter", - "description": "filter specifies the criteria used to select the attributes that should be returned." - }, - "page_size": { - "type": "integer", - "format": "int64", - "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." - }, - "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." - } - } - ], - "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}'" - } - ] - } - }, - "/v1/tenants/{tenant_id}/data/delete": { - "post": { - "summary": "delete data", - "operationId": "data.delete", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/DataDeleteResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "description": "tenant_id represents the unique identifier of the tenant from which the data will be deleted.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "tuple_filter": { - "$ref": "#/definitions/TupleFilter", - "description": "tuple_filter specifies the criteria used to select the tuples that should be deleted." - }, - "attribute_filter": { - "$ref": "#/definitions/AttributeFilter", - "description": "attribute_filter specifies the criteria used to select the attributes that should be deleted." - } - }, - "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." - } - } - ], - "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 relationships", - "operationId": "data.relationships.read", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/RelationshipReadResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "description": "tenant_id represents the unique identifier of the tenant for which relationships are read.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "metadata": { - "$ref": "#/definitions/RelationshipReadRequestMetadata", - "description": "metadata holds additional data related to the request." - }, - "filter": { - "$ref": "#/definitions/TupleFilter", - "description": "filter is used to specify criteria for the data that needs to be read." - }, - "page_size": { - "type": "integer", - "format": "int64", - "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." - }, - "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." - } - } - ], - "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}'" - } - ] - } - }, - "/v1/tenants/{tenant_id}/data/run-bundle": { - "post": { - "summary": "run bundle", - "operationId": "bundle.run", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/BundleRunResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "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." - } - } - ], - "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": "write data", - "operationId": "data.write", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/DataWriteResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "description": "tenant_id represents the unique identifier of the tenant for which data is written.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "metadata": { - "$ref": "#/definitions/DataWriteRequestMetadata", - "description": "metadata holds additional data related to the request." - }, - "tuples": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/Tuple" - }, - "description": "tuples contains the list of tuples (entity-relation-entity triples) that need to be written." - }, - "attributes": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/Attribute" - }, - "description": "attributes contains the list of attributes (entity-attribute-value triples) that need to be written." - } - }, - "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." - } - } - ], - "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": "check api", - "operationId": "permissions.check", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/PermissionCheckResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "description": "Identifier of the tenant, required, and must match the pattern \"[a-zA-Z0-9-,]+\", max 64 bytes.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "metadata": { - "$ref": "#/definitions/PermissionCheckRequestMetadata", - "description": "Metadata associated with this request, required." - }, - "entity": { - "$ref": "#/definitions/Entity", - "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." - }, - "subject": { - "$ref": "#/definitions/Subject", - "description": "Subject for which the permission needs to be checked, required." - }, - "context": { - "$ref": "#/definitions/Context", - "description": "Context associated with this request." - }, - "arguments": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/Argument" - }, - "description": "Additional arguments associated with this request." - } - }, - "description": "PermissionCheckRequest is the request message for the Check method in the Permission service." - } - } - ], - "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 api", - "operationId": "permissions.expand", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/PermissionExpandResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "description": "Identifier of the tenant, required, and must match the pattern \"[a-zA-Z0-9-,]+\", max 64 bytes.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "metadata": { - "$ref": "#/definitions/PermissionExpandRequestMetadata", - "description": "Metadata associated with this request, required." - }, - "entity": { - "$ref": "#/definitions/Entity", - "description": "Entity on which the permission needs to be expanded, required." - }, - "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": "#/definitions/Context", - "description": "Context associated with this request." - }, - "arguments": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/Argument" - }, - "description": "Additional arguments associated with this request." - } - }, - "description": "PermissionExpandRequest is the request message for the Expand method in the Permission service." - } - } - ], - "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": "lookup entity", - "operationId": "permissions.lookupEntity", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/PermissionLookupEntityResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "description": "Identifier of the tenant, required, and must match the pattern \"[a-zA-Z0-9-,]+\", max 64 bytes.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "metadata": { - "$ref": "#/definitions/PermissionLookupEntityRequestMetadata", - "description": "Metadata associated with this request, required." - }, - "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": "#/definitions/Subject", - "description": "Subject for which to check the permission, required." - }, - "context": { - "$ref": "#/definitions/Context", - "description": "Context associated with this request." - } - }, - "description": "PermissionLookupEntityRequest is the request message for the LookupEntity method in the Permission service." - } - } - ], - "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": "lookup entity stream", - "operationId": "permissions.lookupEntityStream", - "responses": { - "200": { - "description": "A successful response.(streaming responses)", - "schema": { - "type": "object", - "properties": { - "result": { - "$ref": "#/definitions/PermissionLookupEntityStreamResponse" - }, - "error": { - "$ref": "#/definitions/Status" - } - }, - "title": "Stream result of PermissionLookupEntityStreamResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "description": "Identifier of the tenant, required, and must match the pattern \"[a-zA-Z0-9-,]+\", max 64 bytes.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "metadata": { - "$ref": "#/definitions/PermissionLookupEntityRequestMetadata", - "description": "Metadata associated with this request, required." - }, - "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": "#/definitions/Subject", - "description": "Subject for which to check the permission, required." - }, - "context": { - "$ref": "#/definitions/Context", - "description": "Context associated with this request." - } - }, - "description": "PermissionLookupEntityRequest is the request message for the LookupEntity method in the Permission service." - } - } - ], - "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": "lookup-subject", - "operationId": "permissions.lookupSubject", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/PermissionLookupSubjectResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "description": "Identifier of the tenant, required, and must match the pattern \"[a-zA-Z0-9-,]+\", max 64 bytes.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "metadata": { - "$ref": "#/definitions/PermissionLookupSubjectRequestMetadata", - "description": "Metadata associated with this request, required." - }, - "entity": { - "$ref": "#/definitions/Entity", - "description": "Entity for which to check the permission, required." - }, - "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": "#/definitions/RelationReference", - "description": "Reference to the subject to lookup." - }, - "context": { - "$ref": "#/definitions/Context", - "description": "Context associated with this request." - } - }, - "description": "PermissionLookupSubjectRequest is the request message for the LookupSubject method in the Permission service." - } - } - ], - "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": "subject permission", - "operationId": "permissions.subjectPermission", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/PermissionSubjectPermissionResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "description": "Identifier of the tenant, required, and must match the pattern \"[a-zA-Z0-9-,]+\", max 64 bytes.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "metadata": { - "$ref": "#/definitions/PermissionSubjectPermissionRequestMetadata", - "description": "Metadata associated with this request, required." - }, - "entity": { - "$ref": "#/definitions/Entity", - "description": "Entity for which to check the permission, required." - }, - "subject": { - "$ref": "#/definitions/Subject", - "description": "Subject for which to check the permission, required." - }, - "context": { - "$ref": "#/definitions/Context", - "description": "Context associated with this request." - } - }, - "description": "PermissionSubjectPermissionRequest is the request message for the SubjectPermission method in the Permission service." - } - } - ], - "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}'" - } - ] - } - }, - "/v1/tenants/{tenant_id}/relationships/delete": { - "post": { - "summary": "delete relationships", - "operationId": "relationships.delete", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/RelationshipDeleteResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "filter": { - "$ref": "#/definitions/TupleFilter" - } - }, - "title": "RelationshipDeleteRequest" - } - } - ], - "tags": [ - "Data" - ] - } - }, - "/v1/tenants/{tenant_id}/relationships/write": { - "post": { - "summary": "write relationships", - "operationId": "relationships.write", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/RelationshipWriteResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "description": "Unique identifier for the tenant with specific constraints.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "metadata": { - "$ref": "#/definitions/RelationshipWriteRequestMetadata", - "description": "Metadata for the request. It's required." - }, - "tuples": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/Tuple" - }, - "description": "List of tuples for the request. Must have between 1 and 100 items." - } - }, - "description": "Represents a request to write relationship data." - } - } - ], - "tags": [ - "Data" - ] - } - }, - "/v1/tenants/{tenant_id}/schemas/list": { - "post": { - "summary": "list schema", - "operationId": "schemas.list", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/SchemaListResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "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.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "page_size": { - "type": "integer", - "format": "int64", - "description": "page_size is the number of tenants to be returned in the response.\nThe value should be between 1 and 100." - }, - "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." - } - } - ], - "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 schema", - "operationId": "schemas.read", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/SchemaReadResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "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.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "metadata": { - "$ref": "#/definitions/SchemaReadRequestMetadata", - "description": "metadata is the additional information needed for the Read request." - } - }, - "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." - } - } - ], - "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 schema", - "operationId": "schemas.write", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/SchemaWriteResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "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.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "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." - } - } - ], - "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": { - "description": "A successful response.(streaming responses)", - "schema": { - "type": "object", - "properties": { - "result": { - "$ref": "#/definitions/WatchResponse" - }, - "error": { - "$ref": "#/definitions/Status" - } - }, - "title": "Stream result of WatchResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/Status" - } - } - }, - "parameters": [ - { - "name": "tenant_id", - "description": "Identifier of the tenant, required, and must match the pattern \"[a-zA-Z0-9-,]+\", max 64 bytes.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "snap_token": { - "type": "string", - "description": "Snap token to be used for watching." - } - }, - "description": "WatchRequest is the request message for the Watch RPC. It contains the\ndetails needed to establish a watch stream." - } - } - ], - "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" - } - ] - } - } - }, - "definitions": { - "AbstractType": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The fully qualified name of this abstract type." - }, - "parameterTypes": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/v1alpha1.Type" - }, - "description": "Parameter types for this abstract 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": {}, - "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(\u0026foo)) {\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 := \u0026pb.Foo{...}\n any, err := anypb.New(foo)\n if err != nil {\n ...\n }\n ...\n foo := \u0026pb.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\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\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": "#/definitions/ComputedAttribute" - }, - "contextAttribute": { - "$ref": "#/definitions/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": "#/definitions/Entity" - }, - "attribute": { - "type": "string", - "title": "Name of the attribute" - }, - "value": { - "$ref": "#/definitions/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": "#/definitions/AttributeType", - "description": "The type of the attribute." - } - }, - "description": "The AttributeDefinition message provides detailed information about a specific attribute." - }, - "AttributeFilter": { - "type": "object", - "properties": { - "entity": { - "$ref": "#/definitions/EntityFilter" - }, - "attributes": { - "type": "array", - "items": { - "type": "string" - }, - "title": "Names of the attributes to be filtered" - } - }, - "description": "AttributeFilter is used to filter attributes based on the entity and attribute names." - }, - "AttributeReadRequestMetadata": { - "type": "object", - "properties": { - "snap_token": { - "type": "string", - "description": "snap_token represents a specific state or \"snapshot\" of the database." - } - }, - "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", - "items": { - "type": "object", - "$ref": "#/definitions/Attribute" - }, - "description": "attributes is a list of the attributes retrieved in the read operation." - }, - "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", - "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" - ], - "default": "ATTRIBUTE_TYPE_UNSPECIFIED", - "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." - }, - "BundleDeleteResponse": { - "type": "object", - "properties": { - "name": { - "type": "string" - } - } - }, - "BundleReadResponse": { - "type": "object", - "properties": { - "bundle": { - "$ref": "#/definitions/DataBundle" - } - } - }, - "BundleRunResponse": { - "type": "object", - "properties": { - "snap_token": { - "type": "string", - "description": "Token related to the bundle execution." - } - }, - "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", - "items": { - "type": "string" - }, - "description": "Identifier or acknowledgment of the written bundle." - } - }, - "description": "BundleWriteResponse is the response for a BundleWriteRequest.\nIt includes a name which could be used as an identifier or acknowledgment." - }, - "CheckResult": { - "type": "string", - "enum": [ - "CHECK_RESULT_UNSPECIFIED", - "CHECK_RESULT_ALLOWED", - "CHECK_RESULT_DENIED" - ], - "default": "CHECK_RESULT_UNSPECIFIED", - "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)." - }, - "CheckedExpr": { - "type": "object", - "properties": { - "referenceMap": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/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": "#/definitions/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": "#/definitions/SourceInfo", - "description": "The source info derived from input that generated the parsed `expr` and\nany optimizations made during the type-checking pass." - }, - "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": "#/definitions/Expr", - "description": "The checked expression. Semantically equivalent to the parsed `expr`, but\nmay have structural differences." - } - }, - "description": "A CEL expression which has been successfully type checked." - }, - "Child": { - "type": "object", - "properties": { - "leaf": { - "$ref": "#/definitions/Leaf", - "description": "Leaf node in the permission tree." - }, - "rewrite": { - "$ref": "#/definitions/Rewrite", - "description": "Rewrite operation in the permission tree." - } - }, - "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": "#/definitions/Expr", - "description": "The range over which var iterates." - }, - "accuVar": { - "type": "string", - "description": "The name of the variable used for accumulation of the result." - }, - "accuInit": { - "$ref": "#/definitions/Expr", - "description": "The initial value of the accumulator." - }, - "loopCondition": { - "$ref": "#/definitions/Expr", - "description": "An expression which can contain iter_var and accu_var.\n\nReturns false when the result has been computed and may be used as\na hint to short-circuit the remainder of the comprehension." - }, - "loopStep": { - "$ref": "#/definitions/Expr", - "description": "An expression which can contain iter_var and accu_var.\n\nComputes the next value of accu_var." - }, - "result": { - "$ref": "#/definitions/Expr", - "description": "An expression which can contain accu_var.\n\nComputes the result." - } - }, - "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 \u003c 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 \u003e 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": { - "type": "string", - "title": "Name of the computed attribute" - } - }, - "description": "ComputedAttribute defines a computed attribute which includes its name." - }, - "ComputedUserSet": { - "type": "object", - "properties": { - "relation": { - "type": "string", - "title": "Relation name" - } - }, - "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", - "format": "int64", - "description": "int64 value." - }, - "uint64Value": { - "type": "string", - "format": "uint64", - "description": "uint64 value." - }, - "doubleValue": { - "type": "number", - "format": "double", - "description": "double value." - }, - "stringValue": { - "type": "string", - "description": "string value." - }, - "bytesValue": { - "type": "string", - "format": "byte", - "description": "bytes value." - }, - "durationValue": { - "type": "string", - "description": "protobuf.Duration value.\n\nDeprecated: duration is no longer considered a builtin cel type." - }, - "timestampValue": { - "type": "string", - "format": "date-time", - "description": "protobuf.Timestamp value.\n\nDeprecated: timestamp is no longer considered a builtin cel type." - } - }, - "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", - "items": { - "type": "object", - "$ref": "#/definitions/Tuple" - }, - "description": "A repeated field of tuples involved in the operation." - }, - "attributes": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/Attribute" - }, - "description": "A repeated field of attributes associated with the operation." - }, - "data": { - "type": "object", - "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": { - "type": "string", - "title": "Name of the context attribute" - } - }, - "description": "ContextAttribute defines a context attribute which includes its name." - }, - "CreateList": { - "type": "object", - "properties": { - "elements": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/Expr" - }, - "description": "The elements part of the list." - }, - "optionalIndices": { - "type": "array", - "items": { - "type": "integer", - "format": "int32" - }, - "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." - } - }, - "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", - "items": { - "type": "object", - "$ref": "#/definitions/Entry" - }, - "description": "The entries in the creation expression." - } - }, - "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", - "items": { - "type": "string" - }, - "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." - }, - "operations": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/v1.Operation" - }, - "description": "'operations' is a repeated field containing multiple Operation messages.\nEach Operation represents a specific action or set of actions to be performed." - } - }, - "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": "#/definitions/DataChange.Operation", - "description": "The operation type." - }, - "tuple": { - "$ref": "#/definitions/Tuple", - "description": "If the change is a tuple." - }, - "attribute": { - "$ref": "#/definitions/Attribute", - "description": "If the change is an 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", - "enum": [ - "OPERATION_UNSPECIFIED", - "OPERATION_CREATE", - "OPERATION_DELETE" - ], - "default": "OPERATION_UNSPECIFIED", - "description": " - OPERATION_UNSPECIFIED: Default operation, not specified.\n - OPERATION_CREATE: Creation operation.\n - OPERATION_DELETE: Deletion operation." - }, - "DataChanges": { - "type": "object", - "properties": { - "snap_token": { - "type": "string", - "description": "The snapshot token." - }, - "data_changes": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/DataChange" - }, - "description": "The list of data changes." - } - }, - "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": "snap_token represents the state of the database after the requested deletions." - } - }, - "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": "snap_token is the token generated after the data write operation, representing a snapshot of the data." - } - }, - "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": "#/definitions/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": "#/definitions/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": "#/definitions/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": "#/definitions/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", - "enum": [ - "REFERENCE_UNSPECIFIED", - "REFERENCE_RELATION", - "REFERENCE_PERMISSION", - "REFERENCE_ATTRIBUTE" - ], - "default": "REFERENCE_UNSPECIFIED", - "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." - }, - "EntityFilter": { - "type": "object", - "properties": { - "type": { - "type": "string", - "title": "Type of the entity" - }, - "ids": { - "type": "array", - "items": { - "type": "string" - }, - "title": "List of entity IDs" - } - }, - "description": "EntityFilter is used to filter entities based on the type and ids." - }, - "Entry": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "int64", - "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." - }, - "fieldKey": { - "type": "string", - "description": "The field key for a message creator statement." - }, - "mapKey": { - "$ref": "#/definitions/Expr", - "description": "The key expression for a map creation statement." - }, - "value": { - "$ref": "#/definitions/Expr", - "description": "Required. The value assigned to the key.\n\nIf the optional_entry field is true, the expression must resolve to an\noptional-typed value. If the optional value is present, the key will be\nset; however, if the optional value is absent, the key will be unset." - }, - "optionalEntry": { - "type": "boolean", - "description": "Whether the key-value pair is optional." - } - }, - "description": "Represents an entry." - }, - "Expand": { - "type": "object", - "properties": { - "entity": { - "$ref": "#/definitions/Entity", - "description": "entity is the entity for which the hierarchical structure is defined." - }, - "permission": { - "type": "string", - "description": "permission is the permission applied to the entity." - }, - "arguments": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/Argument" - }, - "description": "arguments are the additional information or context used to evaluate permissions." - }, - "expand": { - "$ref": "#/definitions/ExpandTreeNode", - "description": "expand contains another hierarchical structure." - }, - "leaf": { - "$ref": "#/definitions/ExpandLeaf", - "description": "leaf contains a set of subjects." - } - }, - "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": "#/definitions/Subjects", - "description": "subjects are used when the leaf is a set of subjects." - }, - "values": { - "$ref": "#/definitions/Values", - "description": "values are used when the leaf node is a set of values." - }, - "value": { - "$ref": "#/definitions/Any", - "description": "value is used when the leaf node is a single value." - } - }, - "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": "#/definitions/ExpandTreeNode.Operation", - "title": "Operation to be applied on this tree node" - }, - "children": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/Expand" - }, - "title": "The children of this tree node" - } - }, - "description": "ExpandTreeNode represents a node in an expansion tree with a specific operation and its children." - }, - "ExpandTreeNode.Operation": { - "type": "string", - "enum": [ - "OPERATION_UNSPECIFIED", - "OPERATION_UNION", - "OPERATION_INTERSECTION", - "OPERATION_EXCLUSION" - ], - "default": "OPERATION_UNSPECIFIED", - "description": "Operation is an enum representing the type of operation to be applied on the tree node." - }, - "Expr": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "int64", - "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." - }, - "constExpr": { - "$ref": "#/definitions/Constant", - "description": "A literal expression." - }, - "identExpr": { - "$ref": "#/definitions/Ident", - "description": "An identifier expression." - }, - "selectExpr": { - "$ref": "#/definitions/Select", - "description": "A field selection expression, e.g. `request.auth`." - }, - "callExpr": { - "$ref": "#/definitions/Expr.Call", - "description": "A call expression, including calls to predefined functions and operators." - }, - "listExpr": { - "$ref": "#/definitions/CreateList", - "description": "A list creation expression." - }, - "structExpr": { - "$ref": "#/definitions/CreateStruct", - "description": "A map or message creation expression." - }, - "comprehensionExpr": { - "$ref": "#/definitions/Comprehension", - "description": "A comprehension expression." - } - }, - "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": "#/definitions/Expr", - "description": "The target of an method call-style expression. For example, `x` in\n`x.f()`." - }, - "function": { - "type": "string", - "description": "Required. The name of the function or method being called." - }, - "args": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/Expr" - }, - "description": "The arguments." - } - }, - "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": "#/definitions/v1alpha1.Type", - "description": "Result type of the function." - }, - "argTypes": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/v1alpha1.Type" - }, - "description": "Argument types of the function." - } - }, - "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": "#/definitions/ComputedUserSet", - "description": "A computed set of users." - }, - "tupleToUserSet": { - "$ref": "#/definitions/TupleToUserSet", - "description": "A tuple to user set conversion." - }, - "computedAttribute": { - "$ref": "#/definitions/ComputedAttribute", - "description": "A computed attribute." - }, - "call": { - "$ref": "#/definitions/v1.Call", - "description": "A call to a function or method." - } - }, - "description": "Leaf represents a leaf node in the permission tree." - }, - "ListType": { - "type": "object", - "properties": { - "elemType": { - "$ref": "#/definitions/v1alpha1.Type", - "description": "The element type." - } - }, - "description": "List type with typed elements, e.g. `list\u003cexample.proto.MyMessage\u003e`." - }, - "MapType": { - "type": "object", - "properties": { - "keyType": { - "$ref": "#/definitions/v1alpha1.Type", - "description": "The type of the key." - }, - "valueType": { - "$ref": "#/definitions/v1alpha1.Type", - "description": "The type of the value." - } - }, - "description": "Map type with parameterized key and value types, e.g. `map\u003cstring, int\u003e`." - }, - "NullValue": { - "type": "string", - "enum": [ - "NULL_VALUE" - ], - "default": "NULL_VALUE", - "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." - }, - "PermissionCheckRequestMetadata": { - "type": "object", - "properties": { - "schema_version": { - "type": "string", - "description": "Version of the schema." - }, - "snap_token": { - "type": "string", - "description": "Token associated with the snap." - }, - "depth": { - "type": "integer", - "format": "int32", - "description": "Depth of the check, must be greater than or equal to 3." - } - }, - "description": "PermissionCheckRequestMetadata is the metadata associated with a PermissionCheckRequest." - }, - "PermissionCheckResponse": { - "type": "object", - "properties": { - "can": { - "$ref": "#/definitions/CheckResult", - "description": "Result of the permission check." - }, - "metadata": { - "$ref": "#/definitions/PermissionCheckResponseMetadata", - "description": "Metadata associated with this response." - } - }, - "description": "PermissionCheckResponse is the response message for the Check method in the Permission service." - }, - "PermissionCheckResponseMetadata": { - "type": "object", - "properties": { - "check_count": { - "type": "integer", - "format": "int32", - "description": "The count of the checks performed." - } - }, - "description": "PermissionCheckResponseMetadata is the metadata associated with a 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": "#/definitions/Child", - "description": "The child related to this permission." - } - }, - "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": "Token associated with the snap." - } - }, - "description": "PermissionExpandRequestMetadata is the metadata associated with a PermissionExpandRequest." - }, - "PermissionExpandResponse": { - "type": "object", - "properties": { - "tree": { - "$ref": "#/definitions/Expand", - "description": "Expansion tree." - } - }, - "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": "Token associated with the snap." - }, - "depth": { - "type": "integer", - "format": "int32", - "description": "Depth of lookup, required, must be greater or equal to 3." - } - }, - "description": "PermissionLookupEntityRequestMetadata is the metadata associated with a PermissionLookupEntityRequest." - }, - "PermissionLookupEntityResponse": { - "type": "object", - "properties": { - "entity_ids": { - "type": "array", - "items": { - "type": "string" - }, - "description": "List of identifiers for entities that match the lookup." - } - }, - "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": "Token associated with the snap." - }, - "depth": { - "type": "integer", - "format": "int32", - "description": "Depth of the check, must be greater than or equal to 3." - } - }, - "description": "PermissionLookupSubjectRequestMetadata is the metadata associated with a PermissionLookupSubjectRequest." - }, - "PermissionLookupSubjectResponse": { - "type": "object", - "properties": { - "subject_ids": { - "type": "array", - "items": { - "type": "string" - }, - "description": "List of identifiers for subjects that match the lookup." - } - }, - "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": "Token associated with the snap." - }, - "only_permission": { - "type": "boolean", - "description": "Whether to only check permissions." - }, - "depth": { - "type": "integer", - "format": "int32", - "description": "Depth of the check, must be greater than or equal to 3." - } - }, - "description": "PermissionSubjectPermissionRequestMetadata is the metadata associated with a PermissionSubjectPermissionRequest." - }, - "PermissionSubjectPermissionResponse": { - "type": "object", - "properties": { - "results": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/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", - "enum": [ - "PRIMITIVE_TYPE_UNSPECIFIED", - "BOOL", - "INT64", - "UINT64", - "DOUBLE", - "STRING", - "BYTES" - ], - "default": "PRIMITIVE_TYPE_UNSPECIFIED", - "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." - }, - "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", - "items": { - "type": "object", - "$ref": "#/definitions/RelationReference" - }, - "description": "A list of references to other relations." - } - }, - "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": { - "type": "object", - "properties": { - "snap_token": { - "type": "string" - } - }, - "title": "RelationshipDeleteResponse" - }, - "RelationshipReadRequestMetadata": { - "type": "object", - "properties": { - "snap_token": { - "type": "string", - "description": "snap_token represents a specific state or \"snapshot\" of the database." - } - }, - "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", - "items": { - "type": "object", - "$ref": "#/definitions/Tuple" - }, - "description": "tuples is a list of the relationships retrieved in the read operation, represented as entity-relation-entity triples." - }, - "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": { - "type": "object", - "properties": { - "schema_version": { - "type": "string" - } - }, - "title": "RelationshipWriteRequestMetadata" - }, - "RelationshipWriteResponse": { - "type": "object", - "properties": { - "snap_token": { - "type": "string" - } - }, - "title": "RelationshipWriteResponse" - }, - "Rewrite": { - "type": "object", - "properties": { - "rewriteOperation": { - "$ref": "#/definitions/Rewrite.Operation", - "description": "The type of rewrite operation to be performed." - }, - "children": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/Child" - }, - "description": "A list of children that are operated upon by the rewrite operation." - } - }, - "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", - "enum": [ - "OPERATION_UNSPECIFIED", - "OPERATION_UNION", - "OPERATION_INTERSECTION", - "OPERATION_EXCLUSION" - ], - "default": "OPERATION_UNSPECIFIED", - "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." - }, - "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": "#/definitions/AttributeType" - }, - "description": "Map of arguments for this rule. The key is the attribute name, and the value is the AttributeType." - }, - "expression": { - "$ref": "#/definitions/CheckedExpr", - "description": "The expression for this rule in the form of a google.api.expr.v1alpha1.CheckedExpr." - } - }, - "description": "The RuleDefinition message provides detailed information about a specific rule." - }, - "SchemaDefinition": { - "type": "object", - "properties": { - "entityDefinitions": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/EntityDefinition" - }, - "description": "Map of entity definitions. The key is the entity name, and the value is the corresponding EntityDefinition." - }, - "ruleDefinitions": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/RuleDefinition" - }, - "description": "Map of rule definitions. The key is the rule name, and the value is the corresponding RuleDefinition." - }, - "references": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/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", - "enum": [ - "REFERENCE_UNSPECIFIED", - "REFERENCE_ENTITY", - "REFERENCE_RULE" - ], - "default": "REFERENCE_UNSPECIFIED", - "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." - }, - "SchemaList": { - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "created_at": { - "type": "string" - } - }, - "title": "SchemaList provides a list of schema versions with their corresponding creation timestamps" - }, - "SchemaListResponse": { - "type": "object", - "properties": { - "head": { - "type": "string", - "title": "head of the schemas is the latest version available for the tenant" - }, - "schemas": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/SchemaList" - }, - "title": "list of schema versions with creation timestamps" - }, - "continuous_token": { - "type": "string", - "description": "continuous_token is a string that can be used to paginate and retrieve the next set of results." - } - }, - "title": "SchemaListResponse is the response message for the List method in the Schema service.\nIt returns a paginated list of schemas" - }, - "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": "#/definitions/SchemaDefinition", - "description": "schema is the SchemaDefinition that represents the read schema." - } - }, - "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": "#/definitions/Expr", - "description": "Required. The target of the selection expression.\n\nFor example, in the select expression `request.auth`, the `request`\nportion of the expression is the `operand`." - }, - "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", - "items": { - "type": "integer", - "format": "int32" - }, - "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] \u003c id_positions[id] \u003c line_offsets[i+1]`. The\ncolumn may be derivd from `id_positions[id] - line_offsets[i]`." - }, - "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": "#/definitions/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 \u003e 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": { - "type": "object", - "$ref": "#/definitions/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": { - "type": "string", - "title": "Type of the subject" - }, - "ids": { - "type": "array", - "items": { - "type": "string" - }, - "title": "List of subject IDs" - }, - "relation": { - "type": "string" - } - }, - "description": "SubjectFilter is used to filter subjects based on the type, ids and relation." - }, - "Subjects": { - "type": "object", - "properties": { - "subjects": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/Subject" - }, - "description": "A list of subjects." - } - }, - "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", - "format": "date-time", - "description": "The time at which the tenant was created." - } - }, - "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": "#/definitions/Tenant", - "description": "tenant is the created tenant information." - } - }, - "description": "TenantCreateResponse is the message returned from the request to create a tenant." - }, - "TenantDeleteResponse": { - "type": "object", - "properties": { - "tenant": { - "$ref": "#/definitions/Tenant", - "description": "tenant is the tenant information that was deleted." - } - }, - "description": "TenantDeleteResponse is the message returned from the request to delete a tenant." - }, - "TenantListRequest": { - "type": "object", - "properties": { - "page_size": { - "type": "integer", - "format": "int64", - "description": "page_size is the number of tenants to be returned in the response.\nThe value should be between 1 and 100." - }, - "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", - "items": { - "type": "object", - "$ref": "#/definitions/Tenant" - }, - "description": "tenants is a list of tenants." - }, - "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": "#/definitions/Entity" - }, - "relation": { - "type": "string" - }, - "subject": { - "$ref": "#/definitions/Subject" - } - }, - "description": "Tuple is a structure that includes an entity, a relation, and a subject." - }, - "TupleFilter": { - "type": "object", - "properties": { - "entity": { - "$ref": "#/definitions/EntityFilter" - }, - "relation": { - "type": "string" - }, - "subject": { - "$ref": "#/definitions/SubjectFilter", - "title": "The subject filter" - } - }, - "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": "#/definitions/TupleSet", - "title": "The tuple set" - }, - "computed": { - "$ref": "#/definitions/ComputedUserSet", - "title": "The computed user set" - } - }, - "description": "TupleToUserSet defines a mapping from tuple sets to computed user sets." - }, - "Values": { - "type": "object", - "properties": { - "values": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Any" - } - } - } - }, - "WatchResponse": { - "type": "object", - "properties": { - "changes": { - "$ref": "#/definitions/DataChanges", - "description": "Changes in the data." - } - }, - "description": "WatchResponse is the response message for the Watch RPC. It contains the\nchanges in the data that are being watched." - }, - "WellKnownType": { - "type": "string", - "enum": [ - "WELL_KNOWN_TYPE_UNSPECIFIED", - "ANY", - "TIMESTAMP", - "DURATION" - ], - "default": "WELL_KNOWN_TYPE_UNSPECIFIED", - "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`." - }, - "v1.Call": { - "type": "object", - "properties": { - "ruleName": { - "type": "string", - "title": "Name of the rule" - }, - "arguments": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/Argument" - }, - "title": "Arguments passed to the rule" - } - }, - "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", - "items": { - "type": "string" - }, - "description": "'relationships_write' is a repeated string field for storing relationship keys\nthat are to be written or created." - }, - "relationships_delete": { - "type": "array", - "items": { - "type": "string" - }, - "description": "'relationships_delete' is a repeated string field for storing relationship keys\nthat are to be deleted or removed." - }, - "attributes_write": { - "type": "array", - "items": { - "type": "string" - }, - "description": "'attributes_write' is a repeated string field for storing attribute keys\nthat are to be written or created." - }, - "attributes_delete": { - "type": "array", - "items": { - "type": "string" - }, - "description": "'attributes_delete' is a repeated string field for storing attribute keys\nthat are to be deleted or removed." - } - }, - "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", - "items": { - "type": "string" - }, - "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]." - }, - "value": { - "$ref": "#/definitions/Constant", - "description": "For references to constants, this may contain the value of the\nconstant if known at compile time." - } - }, - "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": "#/definitions/PrimitiveType", - "description": "Primitive types: `true`, `1u`, `-2.0`, `'string'`, `b'bytes'`." - }, - "wrapper": { - "$ref": "#/definitions/PrimitiveType", - "description": "Wrapper of a primitive type, e.g. `google.protobuf.Int64Value`." - }, - "wellKnown": { - "$ref": "#/definitions/WellKnownType", - "description": "Well-known protobuf type such as `google.protobuf.Timestamp`." - }, - "listType": { - "$ref": "#/definitions/ListType", - "description": "Parameterized list with elements of `list_type`, e.g. `list\u003ctimestamp\u003e`." - }, - "mapType": { - "$ref": "#/definitions/MapType", - "description": "Parameterized map with typed keys and values." - }, - "function": { - "$ref": "#/definitions/FunctionType", - "description": "Function type." - }, - "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\u003cE\u003e`\nwould be a `list_type` whose element type was a `type_param` type\nnamed `E`." - }, - "type": { - "$ref": "#/definitions/v1alpha1.Type", - "description": "Type type.\n\nThe `type` value specifies the target type. e.g. int is type with a\ntarget type of `Primitive.INT`." - }, - "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": "#/definitions/AbstractType", - "description": "Abstract, application defined type." - } - }, - "description": "Represents a CEL type." - } - }, - "securityDefinitions": { - "ApiKeyAuth": { - "type": "apiKey", - "name": "Authorization", - "in": "header" - } - } -} diff --git a/docs/babel.config.js b/docs/babel.config.js deleted file mode 100644 index e00595dae..000000000 --- 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 aa55cabac56a977c7452a5caf36b19c8e51ad16d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122933 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%0AaC#6AFRDsYKv2`8t@@|xFmjXL%ld<#8)@u7 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

B4SCY!E68JNX4m{^A=}j#BODREfdHEFV<<= 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%0Dx(YFDNg@r5YWePIGQ@33bUhNr zSzjl`{-pOegr&zkigo2yC(Cils*y4n?b?Kpq%8oP9K$;j0 zo_W5-{njnz#M4}R#8$-K$fPm-b`iN6O0!BegwpAy#I()kbT!Bg`-GX#KAkb+<+N3Y z*T{KowKmehW$JaaXx6V$WCsB!b9a@eN_AqcSc7y=^vFEXk|-&4)tnfX=16yN0Bb$k zuZ>6g*77|)Eadl(7?PnxXRjo&*{ovF64~Bds0`#w=Hgl0uhUyBMgp zjv>Y+<<%Rhsq;f68L-R318<_@mGOvDo)ytqxnH6@wMx^k$(N~8*VMp}Annm+O#=y; zEY%a8^uP;kc@dkLA~35{P|h078iDL!%2awPqYf_?hjO=n3~v}-^DPNUK@E$N9M&P{ zk;(}<5?FI>?n`wo4&D(WaW$K}B_eXomY0N(Cxp4pv1()x7#Up#8Lm}|*~UO7 zc?SbG=ec>GmVaJ0y5EznR&?A(ZLFWG4>aY4Ak{eg0LjBKKp zyUpU2@M&xF6bP0(n#$5JNIy69f`g+HJ;)IJOa+9ca@2Kl=ykMCHdk<=21zG0)J!+c z3F@e8VE?=kIgHO+;Zje4R8^cRyAxC?IHR7Y7${8j=LM;F&1#TT4WR?Fm{6`n^DG2C zvPOB(KP(FmMqP3E!;0)sznD879<5gQzJAn|A(9E+A9eme?`9O!&I|QIc5bnc1hD) zcZ*FRLmcbHMC)~PjwBVuXaq++3UCH`TBuQQbK8&!%lylw-*~x|B2GtyOd)6h>&;A5~jwqhD_8naP@=s66$_}i2 zWmqP3t8!tp9{n67Mf42lIE@>%k~r3E5qt zdd*DTQmsl&OO>{PX%4j9vCWZ@k^ow!N1d*|g-fn3@uwd~z5bytQPMUj+g^yNiqh7H ziAm5#eRy4SeK=~nh?;a7;z|8w9?m7zU!KKC=KbrFdy?yIPH5Bp)v z#6v;to3Fjr%FmrpyxikAg}N*h@9kETL;OOOOrww>g+K830x9ko4+w9FS@fE6^CT!Tqk5)hGb0T>;`->W%i?_ne)08&%Jzdof zl_dUDN9~!k*dg$0kQZ`}|bOK0%{e%N<;5T!`rvc(p#Yp=NHIql+-9`l>R0?QOsGBx9p6+gTEn zk-nWA=BySG-GZnG21`wAwj{cO!<28Ct!>t`zVH!~oT|{gSR*$4O=*qDHhUXk2D-z~ zuu>Uqed4W$WG9xas?qL;iXKHP?5A_0ZfFr|v+EG1h8Jue>*n+}W%}VwNQkg;Xj7%b z5PPj}*dGa-AT5n;4p#i2%PG#X=}=bKW%E<>MX$F&xZ6}YUur{u>%CCzJewPI$1o8t zabc3Quez#ZKp9i3&3D6g-cKKq7H{T91v7zJ z1?J?}jr}S z&-p>ha_=^aL@&2?K$>Nl*EuzRnv+@$V_=HQDxK8eriI+l0P8Ia74v*$lZZCIOfyH- z5>c8z z`m+u>Sq4>zI{-y}$s8({g|SLfj?XX`@=5vrLD6K6$(Mp9Lu9q%pqv`8v~_iu&E8C_ zeJMNV{wic@~mr7h}RY_mFbeKR3leQe_hyF;#u8Cfa8G0(`{8oG0@>zO(6`8s- zcl@K#l`&3H)+Ftq9X|6amOz42A<^ZR5=c7!Yrw`ZZ2?`t-H1m z|RGR&sR`MMXSOth4~ zNw12_i>NBeUvF2tl`gxb!d!f3z{OD}I77iQ)NHmC^vICYrM#ni5#6tL;_ga@Ls4 z5~b}fYa`+*Q09C|dGm=Pg#lUi-6?bV{?Nura&r%)Ns6R(TM5VZn1Z<-i}O~`lWD8r zr!6zrjM3cg?XQ>{x#)M|mc~vDOl|opU)xtvMTQEY5kS|eS8_FJv

p>54V2dZIEp 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%0Ai1c6WeE5 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 - - -```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 f96a54ce4..000000000 --- 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 9ee9d6530..000000000 --- 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 1a2612e18..000000000 --- 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 b8d09e333..000000000 --- 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 86b4af1b3..000000000 --- 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 f59a3d57a..000000000 --- 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 a86d53ea2..000000000 --- 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/data/write-data.md b/docs/docs/api-overview/data/write-data.md deleted file mode 100644 index 67e870277..000000000 --- a/docs/docs/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/docs/api-overview/permission/_category_.json b/docs/docs/api-overview/permission/_category_.json deleted file mode 100644 index e810c587a..000000000 --- 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 c6bfed58f..000000000 --- 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 998955c19..000000000 --- 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 b87d77a48..000000000 --- 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 0d0e17ff7..000000000 --- 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 8157f3dc3..000000000 --- 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 8fd1e959e..000000000 --- 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 6a3248e26..000000000 --- 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 7a0587d3b..000000000 --- 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 4ae10d83e..000000000 --- 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 e1ebce3ce..000000000 --- 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 20a35d7ea..000000000 --- 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 aca60ef70..000000000 --- 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 bb0c647b0..000000000 --- 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 aabc74164..000000000 --- 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/bundle.md b/docs/docs/bundle.md deleted file mode 100644 index ca7db9ecf..000000000 --- a/docs/docs/bundle.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -id: bundle -title: Bundle Service -sidebar_label: Data Bundles -slug: /api-overview/bundle ---- - -## 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. - -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. - -We believe this functionality will streamline managing authorization data as well as managing this in a central place increase visibility around certain actions/triggers that end up with data creation. - -## How Bundles Works - -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. - -Here's an example body for WriteBundle in this scenario: - -```json -"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", - ], - }, - ], - }, -], -``` - -Operations represent actions that can be performed on relationships and attributes, such as adding or deleting relationships when certain events occur. - -Let's say user:564 creates an organization:789 in your application. According to your authorization logic, this will result in the creation of several authorization data, including relational tuples and attributes, respectively. - -- organization:789#admin@user:564 -- 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. - -An example request of [RunBundle](./api-overview/data/run-bundle.md) for this scenario: - -```json -POST /bundle -BODY -{ - "name": "project_created", - "arguments": { - "creatorID": "564", - "organizationID": "789", - } -} -``` - -This will result in the creation of the following data in Permify: - -- organization:789#admin@user:564 -- organization:789#manager@user:564 -- organization:789$public|boolean:false - -## 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) diff --git a/docs/docs/comparision.md b/docs/docs/comparision.md deleted file mode 100644 index 75fb39c4a..000000000 --- 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 ecc4d10aa..000000000 --- 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 52b54bbbc..000000000 --- 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/enforcement.md b/docs/docs/getting-started/enforcement.md deleted file mode 100644 index df4c80f59..000000000 --- a/docs/docs/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/docs/getting-started/examples/_category_.json b/docs/docs/getting-started/examples/_category_.json deleted file mode 100644 index b3e4f8018..000000000 --- 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 1b918630e..000000000 --- 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 3f14e1f71..000000000 --- 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 9cd831632..000000000 --- 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/examples/mercury.md b/docs/docs/getting-started/examples/mercury.md deleted file mode 100644 index 796c62118..000000000 --- a/docs/docs/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/docs/getting-started/examples/notion.md b/docs/docs/getting-started/examples/notion.md deleted file mode 100644 index 9095d464f..000000000 --- a/docs/docs/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/docs/getting-started/modeling.md b/docs/docs/getting-started/modeling.md deleted file mode 100644 index 2a5fbc60b..000000000 --- a/docs/docs/getting-started/modeling.md +++ /dev/null @@ -1,613 +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 - - -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. - -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/docs/getting-started/sync-data.md b/docs/docs/getting-started/sync-data.md deleted file mode 100644 index 07f0759f0..000000000 --- a/docs/docs/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/docs/getting-started/testing.md b/docs/docs/getting-started/testing.md deleted file mode 100644 index 977fcf2d7..000000000 --- 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 d76986e1f..000000000 --- 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 24b32a32f..000000000 --- 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 c1c204d9a..000000000 --- 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 7f32ade59..000000000 --- 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/brew.md b/docs/docs/installation/brew.md deleted file mode 100644 index 0212df8bf..000000000 --- a/docs/docs/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/docs/installation/container.md b/docs/docs/installation/container.md deleted file mode 100644 index aa0b76869..000000000 --- a/docs/docs/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/docs/installation/google.md b/docs/docs/installation/google.md deleted file mode 100644 index deb30dc91..000000000 --- a/docs/docs/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/docs/installation/helm.md b/docs/docs/installation/helm.md deleted file mode 100644 index 5345d05dc..000000000 --- a/docs/docs/installation/helm.md +++ /dev/null @@ -1,123 +0,0 @@ ---- -title: Helm Chart ---- - -# 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/) - -## Helm Charts for Permify - -Permify provides Helm Charts to facilitate the deployment and management of Permify in Kubernetes environments. Helm Charts encapsulate all the necessary Kubernetes resources and configurations required to run Permify, making it easy to deploy and maintain.([helm-permify-github](https://github.com/Permify/helm-charts)) - -## Helm installation - -### Prerequisite - -Installing the Helm Chart pretty easy but there is a pre-requisite of setting up Kubernetes Cluster. - -If you do not have a Kubernetes cluster you can choose any of the four below options. - -**1. [EKS-Amazon Elastic k8s service](https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html)** - -**2. [GKE-Google k8s engine](https://cloud.google.com/kubernetes-engine)** - -**3. [AKS-Azure kubernetes Service](https://azure.microsoft.com/en-in/products/kubernetes-service)** - -**4. [microk8s](https://microk8s.io/#install-microk8s)** - -### 1.1: Install Helm Chart Using Script - -If you like doing everything from scratch then I would suggest you to install the Helm Chart Using script. - -Run the following scripts - - -```bash -curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 -``` - -```bash -chmod 700 get_helm.sh -``` - -```bash -./get_helm.sh -``` - -You can verify the installation by running the command - -```bash -helm version -``` - -If helm is installed the terminal will provide this as output - -```bash -WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /home/vagrant/.kube/config -version.BuildInfo{Version:"v3.4.0", GitCommit:"7090a89efc8a18f3d8178bf47d2462450349a004", GitTreeState:"clean", GoVersion:"go1.14.10"} -``` - -### 1.2: Install Helm Chart with package Manager - -If you like package manager then you use the following install command based on your preference - - -**Homebrew** - -```bash -brew install helm -``` - -**Chocolatey** - -```bash -choco install kubernetes-helm -``` - -**Scoop** - -``` -scoop install helm -``` - -**Snap** - -```bash -sudo snap install helm --classic -``` - -If helm is installed the terminal will provide this as output - -```bash -WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /home/vagrant/.kube/config -version.BuildInfo{Version:"v3.4.0", GitCommit:"7090a89efc8a18f3d8178bf47d2462450349a004", GitTreeState:"clean", GoVersion:"go1.14.10"} -``` - -## Adding the Permify Helm Charts Repository - -To use Permify Helm Charts, you need to add the Permify Helm Charts repository to Helm. Follow these steps: - -**1.** Open your terminal. - -**2.** Run the following command to add the Permify Helm Charts repository: - -```bash -$ helm repo add permify https://permify.github.io/helm-charts -``` - -**3.** After adding the Permify Helm Charts repository, you can search for available charts using the following command: - -```bash -$ helm search repo permify -``` - -**Installing Permify using Helm Charts** - -Once you've added the Permify Helm Charts repository, you can install Permify using Helm - -```bash -helm install permify permify/permify -``` \ 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 f2a1e21b9..000000000 --- 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 76fb8a56a..000000000 --- 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 0f0135be5..000000000 --- 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/authorization-service.md b/docs/docs/permify-overview/authorization-service.md deleted file mode 100644 index 91d0dc7f7..000000000 --- a/docs/docs/permify-overview/authorization-service.md +++ /dev/null @@ -1,80 +0,0 @@ - -# 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. - -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. - -This approach works well until you have several applications with many services. Managing multiple authorization systems for each application is not a scalable approach, as you can imagine. - -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. - -[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. - -![authz-service](https://user-images.githubusercontent.com/34595361/196884110-147862c9-3657-4f07-831c-3e0d0e39eccf.png) - -[Permify]: https://github.com/Permify/permify - -## Building an Authorization Service is Hard - -Building a centralized authorization service yourself is a hard process, and there are several reasons for that. - -Although centralizing authorization is good in so many ways it has one big tradeoff. These centralized engines are stateless, meaning they don’t store data. They just behave as an engine to manage functionality such as performing access checks. - -For instance; in order to make an access check and compute a decision, you need to load the authorization data and relations from the database and other services. In this case, querying the data needed for access check evaluation presents a significant downside in terms of performance and scalability. - -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. - -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. - -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. - -### 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. - -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. -::: - -[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. - -## 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/permify-overview/infrastructure.md b/docs/docs/permify-overview/infrastructure.md deleted file mode 100644 index 0f29223d2..000000000 --- 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 69c135f29..000000000 --- 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 384c3485b..000000000 --- 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 b55d99d8a..000000000 --- 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 1910705ad..000000000 --- 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/configuration.md b/docs/docs/reference/configuration.md deleted file mode 100644 index 70af81a9c..000000000 --- a/docs/docs/reference/configuration.md +++ /dev/null @@ -1,564 +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 -| ├── preshared -| ├── 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 -| ├── oidc -| ├── issuer -| ├── audience -``` - -#### Glossary - -| Required | Argument | Default | Description | -|----------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [x] | method | - | Authentication method can be either `oidc` or `preshared`. | -| [ ] | enabled | false | switch option authentication config | -| [x] | audience | - | The audience identifies the intended recipients of the token, typically the API or resource server. It ensures tokens are used only by the authorized party. | -| [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-audience | PERMIFY_AUTHN_OIDC_AUDIENCE | 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 | - | [otlp](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 | - -

-
- - -## Testing Config - -You can use the `permify config` command to view the latest state of your configuration. The `Value` part represents the configuration that will be executed, and the `Source` part indicates where this value comes from. There are three options for the "Source" part: (`ENV`, `FILE`, `FLAG`) - -![testing-configuration](https://github.com/Permify/permify/assets/39353278/1b376e81-1fb1-4ccb-b379-a66226d272a2) - -[jaeger]: https://www.jaegertracing.io/ - -[otlp]: https://opentelemetry.io/ - -[zipkin]: https://zipkin.io/ - -[signoz]: https://signoz.io/ diff --git a/docs/docs/reference/contextual-tuples.md b/docs/docs/reference/contextual-tuples.md deleted file mode 100644 index 3765e967b..000000000 --- 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 03bc68179..000000000 --- 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 95eec606e..000000000 --- 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 13fb142d2..000000000 --- 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 76f7bb339..000000000 --- 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 9f9db2d48..000000000 --- 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 7b6e7fb11..000000000 --- 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/abac.md b/docs/docs/use-cases/abac.md deleted file mode 100644 index 23a6cb4d3..000000000 --- a/docs/docs/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/docs/use-cases/custom-roles.md b/docs/docs/use-cases/custom-roles.md deleted file mode 100644 index 4ddf1658e..000000000 --- a/docs/docs/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/docs/use-cases/multi-tenancy.md b/docs/docs/use-cases/multi-tenancy.md deleted file mode 100644 index c8e5be9a8..000000000 --- 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 561b500e3..000000000 --- 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 85d70b6b2..000000000 --- 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 58f719140..000000000 --- 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/package.json b/docs/package.json deleted file mode 100644 index 4284b6d9f..000000000 --- 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/sidebars.js b/docs/sidebars.js deleted file mode 100644 index 7ebddc495..000000000 --- a/docs/sidebars.js +++ /dev/null @@ -1,204 +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", - "api-overview/schema/read-schema", - "api-overview/schema/list-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/src/components/Card/Card.jsx b/docs/src/components/Card/Card.jsx deleted file mode 100644 index 00218170a..000000000 --- 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 a0f025889..000000000 --- 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 e1beed503..000000000 --- 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 28faa8502..000000000 --- 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 65da6a1de..000000000 --- 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 3d8e6c1bf..000000000 --- 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 91217d772..000000000 --- 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 7acbffe7f..000000000 --- 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 b248eb2e5..000000000 --- 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 e1d1c7908..000000000 --- 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 64fc6e37d..000000000 --- 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 9243d1cc7..000000000 --- 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 666feb6a1..000000000 --- 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 e20261f45..000000000 --- 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 1ecb8ee9a..000000000 --- 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 e69de29bb..000000000 diff --git a/docs/static/img/docusaurus.png b/docs/static/img/docusaurus.png deleted file mode 100644 index f458149e3c8f53335f28fbc162ae67f55575c881..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5142 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 F{0H{b<4^zq diff --git a/docs/static/img/favicon.ico b/docs/static/img/favicon.ico deleted file mode 100644 index 9b582bdd2ab145b13b3d0de2b95d0bffca268cab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18057 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 D$2(WI diff --git a/docs/static/img/logo.svg b/docs/static/img/logo.svg deleted file mode 100644 index faff98f98..000000000 --- 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 ff1cbe68893d205dc0a6821bfa74d8e7d25c09cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25102 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 Js!v|N^*`%a2QvTw diff --git a/docs/static/img/tutorial/localeDropdown.png b/docs/static/img/tutorial/localeDropdown.png deleted file mode 100644 index d7163f96752499e2e39e771d4d5e9e42c68677aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30020 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 diff --git a/docs/static/img/undraw_docusaurus_mountain.svg b/docs/static/img/undraw_docusaurus_mountain.svg deleted file mode 100644 index 431cef2f7..000000000 --- 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 e41705043..000000000 --- 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 a05cc03dd..000000000 --- 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 6f4756980..000000000 --- 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/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 762d98a5a..000000000 --- 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 5e5154004..000000000 --- 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 b9d9b56c2..000000000 --- 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 17474d7ea..000000000 --- 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 ff4c90864..000000000 --- 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 2044a6162..000000000 --- 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 96b75d1a3..000000000 --- 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 37eb812f9..000000000 --- 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/api-overview/write-schema.md b/docs/versioned_docs/version-0.2.x/api-overview/write-schema.md deleted file mode 100644 index f97a9f84d..000000000 --- a/docs/versioned_docs/version-0.2.x/api-overview/write-schema.md +++ /dev/null @@ -1,72 +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. - -### 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) - -### 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 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 3bbc347f5..000000000 --- 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 a1f5560d0..000000000 --- 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 7f32ade59..000000000 --- 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 dcb3614a2..000000000 --- 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 997c89423..000000000 --- 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 9f9db2d48..000000000 --- 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 7c4f94df2..000000000 --- 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 0891e84bd..000000000 --- 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 88ecd8b03..000000000 --- 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 0850cbc6d..000000000 --- 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 17933eada..000000000 --- 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 ed2596490..000000000 --- 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 52b54bbbc..000000000 --- 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 c2f807ac6..000000000 --- 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 4be6f25d3..000000000 --- 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 597754dd9..000000000 --- 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 1d01611a5..000000000 --- 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 24b32a32f..000000000 --- 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 8e81d855f..000000000 --- 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 32c7dd297..000000000 --- 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:** [otlp](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 194c12474..000000000 --- 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 0f0135be5..000000000 --- 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 0810c6c2f..000000000 --- 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 9cc9733ce..000000000 --- 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 3b889d419..000000000 --- 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 00947b272..000000000 --- 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 b55d99d8a..000000000 --- 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 58c57b51d..000000000 --- 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 d9d734f5d..000000000 --- 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 9f6a31931..000000000 --- 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 af2094da8..000000000 --- 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 5e5154004..000000000 --- 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 f91d5b460..000000000 --- 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 fff42fdbf..000000000 --- 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 df568adb6..000000000 --- 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 33ed99a8c..000000000 --- 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 5d6601174..000000000 --- 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 bca5fd5ed..000000000 --- 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 d63b7e683..000000000 --- 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 eb70b30c1..000000000 --- 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 0646ff4e7..000000000 --- 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 8fd1e959e..000000000 --- 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 47a0e1ff1..000000000 --- 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 9771416df..000000000 --- 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 412ddaec4..000000000 --- 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 d8bd4a431..000000000 --- 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 6df7749f6..000000000 --- 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 52b54bbbc..000000000 --- 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 d7ee5f123..000000000 --- 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 b3e4f8018..000000000 --- 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 53a309a6d..000000000 --- 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 9e66f806e..000000000 --- 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 f3ed3d8d8..000000000 --- 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 ba4b42c36..000000000 --- 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 4f0243a2a..000000000 --- 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 124932d5d..000000000 --- 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 c855058fd..000000000 --- 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 24b32a32f..000000000 --- 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 c1c204d9a..000000000 --- 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 7f32ade59..000000000 --- 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 e73880767..000000000 --- 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 4802b0786..000000000 --- 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 dcb3614a2..000000000 --- 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 f2a1e21b9..000000000 --- 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 330db2e29..000000000 --- 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 c6906468e..000000000 --- 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 0f0135be5..000000000 --- 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 0810c6c2f..000000000 --- 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 9cc9733ce..000000000 --- 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 8c58eaace..000000000 --- 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 00947b272..000000000 --- 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 b55d99d8a..000000000 --- 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 21ca4553c..000000000 --- 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 | - | [otlp](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 ccefde53b..000000000 --- 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 473036673..000000000 --- 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 9f6a31931..000000000 --- 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 6fae082cd..000000000 --- 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 9f9db2d48..000000000 --- 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 81aacd1ac..000000000 --- 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 787a5acca..000000000 --- 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 836945f66..000000000 --- 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 f7dc5b947..000000000 --- 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 4a97b41b9..000000000 --- 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 82767daa1..000000000 --- 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 597ab55ba..000000000 --- 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 64e4a6d1d..000000000 --- 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 5e5154004..000000000 --- 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 f91d5b460..000000000 --- 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 ab24405cd..000000000 --- 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 d742de2e2..000000000 --- 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 77c9f1c6b..000000000 --- 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 27ab61280..000000000 --- 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 c2ff2eaf7..000000000 --- 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 bca5fd5ed..000000000 --- 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 d63b7e683..000000000 --- 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 eb70b30c1..000000000 --- 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 0646ff4e7..000000000 --- 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 8fd1e959e..000000000 --- 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 47a0e1ff1..000000000 --- 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 9771416df..000000000 --- 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 412ddaec4..000000000 --- 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 d8bd4a431..000000000 --- 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 f65c41fd5..000000000 --- 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 921fac97e..000000000 --- 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 a73e70f98..000000000 --- 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 52b54bbbc..000000000 --- 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 9a810df31..000000000 --- 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 b3e4f8018..000000000 --- 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/facebook-groups.md b/docs/versioned_docs/version-0.4.x/getting-started/examples/facebook-groups.md deleted file mode 100644 index 1b918630e..000000000 --- a/docs/versioned_docs/version-0.4.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.4.x/getting-started/examples/google-docs.md b/docs/versioned_docs/version-0.4.x/getting-started/examples/google-docs.md deleted file mode 100644 index 3f14e1f71..000000000 --- a/docs/versioned_docs/version-0.4.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.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 9095d464f..000000000 --- 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 ba07ed5f3..000000000 --- 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 98a1bca6b..000000000 --- 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 950b1b447..000000000 --- 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 c855058fd..000000000 --- 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 24b32a32f..000000000 --- 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/aws.md b/docs/versioned_docs/version-0.4.x/installation/aws.md deleted file mode 100644 index c1c204d9a..000000000 --- a/docs/versioned_docs/version-0.4.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.4.x/installation/azure.md b/docs/versioned_docs/version-0.4.x/installation/azure.md deleted file mode 100644 index 7f32ade59..000000000 --- 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 c56a06d8b..000000000 --- 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 c91a1ac4a..000000000 --- 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 deb30dc91..000000000 --- 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 f2a1e21b9..000000000 --- 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 4dc5560db..000000000 --- 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 0f0135be5..000000000 --- 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 e8df72d39..000000000 --- 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 9cc9733ce..000000000 --- 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 903123620..000000000 --- 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 73f6ca4da..000000000 --- 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 b55d99d8a..000000000 --- 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 f281a1a10..000000000 --- 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 3572f52f6..000000000 --- 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 | - | [otlp](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 d92c536db..000000000 --- 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 ccefde53b..000000000 --- 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 919974f5b..000000000 --- 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 128105260..000000000 --- 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 6fae082cd..000000000 --- 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 9f9db2d48..000000000 --- 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 7b6e7fb11..000000000 --- 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 e3945ec07..000000000 --- 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 1df64794f..000000000 --- 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 00a455b44..000000000 --- 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 787a5acca..000000000 --- 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 f5438bf04..000000000 --- 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 f7dc5b947..000000000 --- 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 36ad8d54a..000000000 --- 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 4a97b41b9..000000000 --- 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 f5cf485b4..000000000 --- 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 aa48ce152..000000000 --- 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 08346b464..000000000 --- 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 5e5154004..000000000 --- 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 d245a3c37..000000000 --- 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 fe8ba7d4d..000000000 --- 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 248d34d93..000000000 --- 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 384f766f9..000000000 --- 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 73199ac76..000000000 --- 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 f91d5b460..000000000 --- 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 e85b48aff..000000000 --- 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 ea91bb1f5..000000000 --- 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 a14679dbb..000000000 --- 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 baaacd6bd..000000000 --- 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 9048b33ca..000000000 --- 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 8fd1e959e..000000000 --- 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 bb4f94596..000000000 --- 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 9771416df..000000000 --- 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 69a7a6ff7..000000000 --- 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 a74a11e8b..000000000 --- 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 f65c41fd5..000000000 --- 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 ff48c0979..000000000 --- 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 75fb39c4a..000000000 --- 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 52b54bbbc..000000000 --- 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 df4c80f59..000000000 --- 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 b3e4f8018..000000000 --- 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 1b918630e..000000000 --- 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 3f14e1f71..000000000 --- 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/instagram.md b/docs/versioned_docs/version-0.5.x/getting-started/examples/instagram.md deleted file mode 100644 index 9cd831632..000000000 --- a/docs/versioned_docs/version-0.5.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.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 c755aafc9..000000000 --- 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 9095d464f..000000000 --- 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 4a0a8316f..000000000 --- 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 b8726a023..000000000 --- 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/getting-started/testing.md b/docs/versioned_docs/version-0.5.x/getting-started/testing.md deleted file mode 100644 index 977fcf2d7..000000000 --- a/docs/versioned_docs/version-0.5.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.5.x/installation.md b/docs/versioned_docs/version-0.5.x/installation.md deleted file mode 100644 index c855058fd..000000000 --- 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 24b32a32f..000000000 --- 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 c1c204d9a..000000000 --- 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 7f32ade59..000000000 --- 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 0212df8bf..000000000 --- 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 aa0b76869..000000000 --- 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 deb30dc91..000000000 --- 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/kubernetes.md b/docs/versioned_docs/version-0.5.x/installation/kubernetes.md deleted file mode 100644 index f2a1e21b9..000000000 --- a/docs/versioned_docs/version-0.5.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.5.x/installation/overview.md b/docs/versioned_docs/version-0.5.x/installation/overview.md deleted file mode 100644 index 76fb8a56a..000000000 --- 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 0f0135be5..000000000 --- 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 55a6906d9..000000000 --- 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 0f29223d2..000000000 --- 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 69c135f29..000000000 --- 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 384c3485b..000000000 --- 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 b55d99d8a..000000000 --- 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/cache.md b/docs/versioned_docs/version-0.5.x/reference/cache.md deleted file mode 100644 index 1910705ad..000000000 --- a/docs/versioned_docs/version-0.5.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.5.x/reference/configuration.md b/docs/versioned_docs/version-0.5.x/reference/configuration.md deleted file mode 100644 index 97bd9ce0a..000000000 --- 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 | - | [otlp](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 ff3a806b2..000000000 --- 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 ab02007ed..000000000 --- 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 8f1a21786..000000000 --- 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 128105260..000000000 --- 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 6fae082cd..000000000 --- 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 9f9db2d48..000000000 --- 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 7b6e7fb11..000000000 --- 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 a4acee335..000000000 --- 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 4ddf1658e..000000000 --- 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/multi-tenancy.md b/docs/versioned_docs/version-0.5.x/use-cases/multi-tenancy.md deleted file mode 100644 index c8e5be9a8..000000000 --- a/docs/versioned_docs/version-0.5.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_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 561b500e3..000000000 --- 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 85d70b6b2..000000000 --- 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 931d562b0..000000000 --- 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 5e5154004..000000000 --- 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 d8d37140a..000000000 --- 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 d175eda80..000000000 --- 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 f96a54ce4..000000000 --- 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 9ee9d6530..000000000 --- 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 1a2612e18..000000000 --- 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 b8d09e333..000000000 --- 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 86b4af1b3..000000000 --- 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 f59a3d57a..000000000 --- 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 a86d53ea2..000000000 --- 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 67e870277..000000000 --- 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 e810c587a..000000000 --- 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/check-api.md b/docs/versioned_docs/version-0.6.x/api-overview/permission/check-api.md deleted file mode 100644 index c6bfed58f..000000000 --- a/docs/versioned_docs/version-0.6.x/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/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 998955c19..000000000 --- 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 b87d77a48..000000000 --- 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 0d0e17ff7..000000000 --- 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 8157f3dc3..000000000 --- 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 8fd1e959e..000000000 --- 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 4ae10d83e..000000000 --- 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 e1ebce3ce..000000000 --- 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 20a35d7ea..000000000 --- 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 aca60ef70..000000000 --- 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 bb0c647b0..000000000 --- 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 aabc74164..000000000 --- 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/bundle.md b/docs/versioned_docs/version-0.6.x/bundle.md deleted file mode 100644 index ca7db9ecf..000000000 --- a/docs/versioned_docs/version-0.6.x/bundle.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -id: bundle -title: Bundle Service -sidebar_label: Data Bundles -slug: /api-overview/bundle ---- - -## 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. - -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. - -We believe this functionality will streamline managing authorization data as well as managing this in a central place increase visibility around certain actions/triggers that end up with data creation. - -## How Bundles Works - -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. - -Here's an example body for WriteBundle in this scenario: - -```json -"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", - ], - }, - ], - }, -], -``` - -Operations represent actions that can be performed on relationships and attributes, such as adding or deleting relationships when certain events occur. - -Let's say user:564 creates an organization:789 in your application. According to your authorization logic, this will result in the creation of several authorization data, including relational tuples and attributes, respectively. - -- organization:789#admin@user:564 -- 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. - -An example request of [RunBundle](./api-overview/data/run-bundle.md) for this scenario: - -```json -POST /bundle -BODY -{ - "name": "project_created", - "arguments": { - "creatorID": "564", - "organizationID": "789", - } -} -``` - -This will result in the creation of the following data in Permify: - -- organization:789#admin@user:564 -- organization:789#manager@user:564 -- organization:789$public|boolean:false - -## 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) 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 75fb39c4a..000000000 --- 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/examples.md b/docs/versioned_docs/version-0.6.x/examples.md deleted file mode 100644 index ecc4d10aa..000000000 --- a/docs/versioned_docs/version-0.6.x/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/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 52b54bbbc..000000000 --- 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/enforcement.md b/docs/versioned_docs/version-0.6.x/getting-started/enforcement.md deleted file mode 100644 index df4c80f59..000000000 --- a/docs/versioned_docs/version-0.6.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.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 b3e4f8018..000000000 --- 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 1b918630e..000000000 --- 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 3f14e1f71..000000000 --- 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 9cd831632..000000000 --- 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 796c62118..000000000 --- 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 9095d464f..000000000 --- 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 0e3231f52..000000000 --- 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 07f0759f0..000000000 --- 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 977fcf2d7..000000000 --- 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 c428e1f5e..000000000 --- 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 24b32a32f..000000000 --- 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 c1c204d9a..000000000 --- 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 7f32ade59..000000000 --- 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 0212df8bf..000000000 --- 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 aa0b76869..000000000 --- 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 deb30dc91..000000000 --- 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 f2a1e21b9..000000000 --- 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/installation/overview.md b/docs/versioned_docs/version-0.6.x/installation/overview.md deleted file mode 100644 index 76fb8a56a..000000000 --- a/docs/versioned_docs/version-0.6.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.6.x/permify-overview/_category_.json b/docs/versioned_docs/version-0.6.x/permify-overview/_category_.json deleted file mode 100644 index 0f0135be5..000000000 --- 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 55a6906d9..000000000 --- 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 0f29223d2..000000000 --- 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 69c135f29..000000000 --- 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 384c3485b..000000000 --- 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 b55d99d8a..000000000 --- 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 1910705ad..000000000 --- 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 36fa44401..000000000 --- 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 | - | [otlp](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/contextual-tuples.md b/docs/versioned_docs/version-0.6.x/reference/contextual-tuples.md deleted file mode 100644 index 3765e967b..000000000 --- a/docs/versioned_docs/version-0.6.x/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/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 d005f2592..000000000 --- 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/reference/snap-tokens.md b/docs/versioned_docs/version-0.6.x/reference/snap-tokens.md deleted file mode 100644 index 95eec606e..000000000 --- a/docs/versioned_docs/version-0.6.x/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/versioned_docs/version-0.6.x/reference/tracing.md b/docs/versioned_docs/version-0.6.x/reference/tracing.md deleted file mode 100644 index 13fb142d2..000000000 --- a/docs/versioned_docs/version-0.6.x/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/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 76f7bb339..000000000 --- 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 9f9db2d48..000000000 --- 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 7b6e7fb11..000000000 --- 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 23a6cb4d3..000000000 --- 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 4ddf1658e..000000000 --- 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 c8e5be9a8..000000000 --- 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_docs/version-0.6.x/use-cases/rebac.md b/docs/versioned_docs/version-0.6.x/use-cases/rebac.md deleted file mode 100644 index 561b500e3..000000000 --- a/docs/versioned_docs/version-0.6.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.6.x/use-cases/simple-rbac.md b/docs/versioned_docs/version-0.6.x/use-cases/simple-rbac.md deleted file mode 100644 index 85d70b6b2..000000000 --- a/docs/versioned_docs/version-0.6.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_sidebars/version-0.2.x-sidebars.json b/docs/versioned_sidebars/version-0.2.x-sidebars.json deleted file mode 100644 index 0a0e671f8..000000000 --- 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 7ef0942b2..000000000 --- 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 fa2857fad..000000000 --- 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 2390f4576..000000000 --- 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 f3bed79b5..000000000 --- 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 841e5a8d3..000000000 --- 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 89b2054f8..000000000 --- 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== From 46a62ec1969661f7bcf5b531436b21baf126cd4f Mon Sep 17 00:00:00 2001 From: EgeAytin Date: Tue, 19 Mar 2024 14:20:57 +0300 Subject: [PATCH 50/70] docs: add new version of the docs --- docs/.DS_Store | Bin 6148 -> 10244 bytes docs/README.md | 32 + docs/api-reference/.DS_Store | Bin 0 -> 6148 bytes docs/api-reference/bundle/delete-bundle.mdx | 10 + docs/api-reference/bundle/read-bundle.mdx | 10 + docs/api-reference/bundle/write-bundle.mdx | 86 + docs/api-reference/data/delete-data.mdx | 4 + .../data/delete-relationships.mdx | 4 + docs/api-reference/data/read-attributes.mdx | 6 + .../api-reference/data/read-relationships.mdx | 6 + docs/api-reference/data/run-bundle.mdx | 10 + docs/api-reference/data/write-data.mdx | 447 +++ .../data/write-relationships.mdx | 4 + docs/api-reference/introduction.mdx | 108 + docs/api-reference/openapi.json | 3510 +++++++++++++++++ docs/api-reference/openapi2.json | 195 + docs/api-reference/permission/check-api.mdx | 176 + docs/api-reference/permission/expand-api.mdx | 14 + .../permission/lookup-entity-stream.mdx | 6 + .../permission/lookup-entity.mdx | 50 + .../permission/lookup-subject.mdx | 8 + .../permission/subject-permission.mdx | 8 + docs/api-reference/schema/list-schema.mdx | 13 + docs/api-reference/schema/read-schema.mdx | 13 + docs/api-reference/schema/write-schema.mdx | 25 + docs/api-reference/tenancy/create-tenant.mdx | 10 + docs/api-reference/tenancy/delete-tenant.mdx | 4 + docs/api-reference/tenancy/list-tenants.mdx | 4 + docs/api-reference/watch/watch-changes.mdx | 70 + docs/development.mdx | 98 + docs/favicon.svg | 10 + docs/getting-started/enforcement.mdx | 108 + .../examples/facebook-groups.mdx | 536 +++ docs/getting-started/examples/google-docs.mdx | 330 ++ docs/getting-started/examples/instagram.mdx | 324 ++ docs/getting-started/examples/intro.mdx | 17 + docs/getting-started/examples/mercury.mdx | 157 + docs/getting-started/examples/notion.mdx | 539 +++ docs/getting-started/modeling.mdx | 619 +++ docs/getting-started/quickstart.mdx | 273 ++ docs/getting-started/sync-data.mdx | 472 +++ docs/getting-started/testing.mdx | 279 ++ docs/images/checks-passed.png | Bin 0 -> 160724 bytes docs/images/hero-dark.svg | 161 + docs/images/hero-light.svg | 155 + docs/introduction/authorization-service.mdx | 89 + docs/introduction/faqs.mdx | 129 + docs/introduction/intro.mdx | 109 + docs/logo/.DS_Store | Bin 0 -> 6148 bytes docs/logo/dark.svg | 9 + docs/logo/light.svg | 9 + docs/mint.json | 181 + docs/operations/bundle.mdx | 82 + docs/operations/cache.mdx | 98 + docs/operations/contextual-tuples.mdx | 193 + docs/operations/observability.mdx | 58 + docs/operations/snap-tokens.mdx | 61 + docs/quickstart.mdx | 86 + docs/setting-up/configuration.mdx | 549 +++ docs/setting-up/installation/.DS_Store | Bin 0 -> 6148 bytes docs/setting-up/installation/aws.mdx | 185 + docs/setting-up/installation/brew.mdx | 85 + docs/setting-up/installation/container.mdx | 74 + docs/setting-up/installation/google.mdx | 271 ++ docs/setting-up/installation/helm.mdx | 119 + docs/setting-up/installation/intro.mdx | 58 + docs/setting-up/installation/kubernetes.mdx | 171 + docs/snippets/snippet-intro.mdx | 4 + docs/use-cases/.DS_Store | Bin 0 -> 6148 bytes docs/use-cases/abac.mdx | 626 +++ docs/use-cases/custom-roles.mdx | 75 + docs/use-cases/multi-tenancy.mdx | 137 + docs/use-cases/rbac.mdx | 126 + docs/use-cases/rebac.mdx | 423 ++ 74 files changed, 12918 insertions(+) create mode 100644 docs/README.md create mode 100644 docs/api-reference/.DS_Store create mode 100644 docs/api-reference/bundle/delete-bundle.mdx create mode 100644 docs/api-reference/bundle/read-bundle.mdx create mode 100644 docs/api-reference/bundle/write-bundle.mdx create mode 100644 docs/api-reference/data/delete-data.mdx create mode 100644 docs/api-reference/data/delete-relationships.mdx create mode 100644 docs/api-reference/data/read-attributes.mdx create mode 100644 docs/api-reference/data/read-relationships.mdx create mode 100644 docs/api-reference/data/run-bundle.mdx create mode 100644 docs/api-reference/data/write-data.mdx create mode 100644 docs/api-reference/data/write-relationships.mdx create mode 100644 docs/api-reference/introduction.mdx create mode 100644 docs/api-reference/openapi.json create mode 100644 docs/api-reference/openapi2.json create mode 100644 docs/api-reference/permission/check-api.mdx create mode 100644 docs/api-reference/permission/expand-api.mdx create mode 100644 docs/api-reference/permission/lookup-entity-stream.mdx create mode 100644 docs/api-reference/permission/lookup-entity.mdx create mode 100644 docs/api-reference/permission/lookup-subject.mdx create mode 100644 docs/api-reference/permission/subject-permission.mdx create mode 100644 docs/api-reference/schema/list-schema.mdx create mode 100644 docs/api-reference/schema/read-schema.mdx create mode 100644 docs/api-reference/schema/write-schema.mdx create mode 100644 docs/api-reference/tenancy/create-tenant.mdx create mode 100644 docs/api-reference/tenancy/delete-tenant.mdx create mode 100644 docs/api-reference/tenancy/list-tenants.mdx create mode 100644 docs/api-reference/watch/watch-changes.mdx create mode 100644 docs/development.mdx create mode 100644 docs/favicon.svg create mode 100644 docs/getting-started/enforcement.mdx create mode 100644 docs/getting-started/examples/facebook-groups.mdx create mode 100644 docs/getting-started/examples/google-docs.mdx create mode 100644 docs/getting-started/examples/instagram.mdx create mode 100644 docs/getting-started/examples/intro.mdx create mode 100644 docs/getting-started/examples/mercury.mdx create mode 100644 docs/getting-started/examples/notion.mdx create mode 100644 docs/getting-started/modeling.mdx create mode 100644 docs/getting-started/quickstart.mdx create mode 100644 docs/getting-started/sync-data.mdx create mode 100644 docs/getting-started/testing.mdx create mode 100644 docs/images/checks-passed.png create mode 100644 docs/images/hero-dark.svg create mode 100644 docs/images/hero-light.svg create mode 100644 docs/introduction/authorization-service.mdx create mode 100644 docs/introduction/faqs.mdx create mode 100644 docs/introduction/intro.mdx create mode 100644 docs/logo/.DS_Store create mode 100644 docs/logo/dark.svg create mode 100644 docs/logo/light.svg create mode 100644 docs/mint.json create mode 100644 docs/operations/bundle.mdx create mode 100644 docs/operations/cache.mdx create mode 100644 docs/operations/contextual-tuples.mdx create mode 100644 docs/operations/observability.mdx create mode 100644 docs/operations/snap-tokens.mdx create mode 100644 docs/quickstart.mdx create mode 100644 docs/setting-up/configuration.mdx create mode 100644 docs/setting-up/installation/.DS_Store create mode 100644 docs/setting-up/installation/aws.mdx create mode 100644 docs/setting-up/installation/brew.mdx create mode 100644 docs/setting-up/installation/container.mdx create mode 100644 docs/setting-up/installation/google.mdx create mode 100644 docs/setting-up/installation/helm.mdx create mode 100644 docs/setting-up/installation/intro.mdx create mode 100644 docs/setting-up/installation/kubernetes.mdx create mode 100644 docs/snippets/snippet-intro.mdx create mode 100644 docs/use-cases/.DS_Store create mode 100644 docs/use-cases/abac.mdx create mode 100644 docs/use-cases/custom-roles.mdx create mode 100644 docs/use-cases/multi-tenancy.mdx create mode 100644 docs/use-cases/rbac.mdx create mode 100644 docs/use-cases/rebac.mdx diff --git a/docs/.DS_Store b/docs/.DS_Store index 74e6c6d5d2180f79466cdf141ee0b6c4c3ff017b..919a104a48c5440d55fa8835e0a30e26c58d6f21 100644 GIT binary patch literal 10244 zcmeI1L2DC16oB8RY23t?Rw%ZD;2tU{q%~>U(jr2P5$wrIL=P(MCL7y8vb$tA291$O zkM`tA{SBTxc=n=L+JoYu2QLNDv*68x`etUE&Lo?p9z-oOW%f-n@6EjVW@e{v0su;? zV#EP708lX#IXr+5pG4|OZc730o6{&2vSV2PMg*d`be6Ksx~vyAOg6HWXk& zh~G`fK|Wh`>}=7>qSi*%E*2a&7TZ<=eZl_FAytj2k-^BV%&Jw*xVl?$QhNCY`Lw9J zt68sn)v}7svYx$G$XAz!M=n^Vs~60CnHm?2JgVHhUN8--nzAaU;m~$6tU^`8YItdQ ztX7+vjYYM@^i(~n)#j(LJe`=W*Td@Q$@sa2<-f)%`@aq+JJCYC3_!Lp7kz{T$k0YV^jrMCqS;>(eTpU2-* z5Iy=SS$i<{ZzTW;ER*qsa2zG{pNz^8z2ExSTF?7B=DZ5M*rdJ+ZSbCT(h>dmO8gk0 zJYD2yfq~$2l&}S+;1gudcGKnkd$}DvbAqw#*f#I$e)I*uaqD9W;sUXdo+bLZxz5+| zeqGwQ{OA~4>`hzV55Ib`-yPC#1bek{&dNAKl3S`0DX~>W@E7dh^*qcW+1Z(iQwvVuYuPewxV9rc~Wl zmdbHI9p1n1Yt8-8))vpOo1Wko`-d)!dw~#~z#XB}lEa-XZ~yk$GFq!8=I}h3nI%Wd z+)N^>&Bf>2j+Q3`F}_zj9W7)9_>5FsCvTqnYw@!X&8&}<*lPA^kDrI`n&M3CV8?#` zwHz5lOD0J`5|9KW0ZBj-IG6^cqZ36MWF?c$D-y9zT%l_*0mPE)7h*a?+CxCaF2*HvfK&EdbRu?#s9wm_B^f; delta 140 zcmZn(XfcprU|?W$DortDU=RQ@Ie-{MGpJ516gFoBGFTas7)lv38FCm>Qi_vvlJfI& zK=KSsU=pYqNCPoY6I_0?qQp}6#q1m$g3J&_0^C5_6^K7~%SZ^ZZEp~&um-6+gb?XRZKbBEfC{n8f&;Kb;yS9N9iWh7ENHTr z_6DOgSyiH)!ztht_-zXCyW5~4t!YVPs=UALr*eCeM$vGQ4&nEoFFU6n@2|(znDN$_ z)y$kHx)_2yFo@}vVtWqh9-3`t{)cIvKku8QMee$GJMSasZPJP+bWI7(=?1?!rL?p; z92{+mGx?k+IiK1wO>Wy_ZJu&EZGhW|X5gBXHEQwwu-Z*tX9cfPOrh1L7@LQ@Py4t> z6Fe_5%82f5rNj%Elp;(npK-2HC8}VvIjV-w9^J|32HMzM{5NnhT1_5b7da0rqTo;; zVdrln!Jjp(*=&AGp{tz&P64Mtt^m&u0SaTNF;gg~4pedr0QBINhGRJ=0t0pcLyeh2 zcwjZ!v`G4u7EILXDY1)t!XQjB#{kp?4@k&&VQ>;Uq$Zu67DI1#AU&)oGj8 z|C7!8f4h@Aa|$>G{*?m4iw032m!#L$%Ej?o8=zdFuyI_bPDA?n6Mz;3}uUk1FsDY3j|d literal 0 HcmV?d00001 diff --git a/docs/api-reference/bundle/delete-bundle.mdx b/docs/api-reference/bundle/delete-bundle.mdx new file mode 100644 index 000000000..21fe3193c --- /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 000000000..e68701005 --- /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/api-reference/bundle/write-bundle.mdx b/docs/api-reference/bundle/write-bundle.mdx new file mode 100644 index 000000000..9688a7e6e --- /dev/null +++ b/docs/api-reference/bundle/write-bundle.mdx @@ -0,0 +1,86 @@ +--- +title: Write Bundle +openapi: post /v1/tenants/{tenant_id}/bundle/write +--- + +## 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](../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. + +We believe this functionality will streamline managing authorization data as well as managing this in a central place increase visibility around certain actions/triggers that end up with data creation. + +## How Bundles Works + +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 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: + +```json +"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", + ], + }, + ], + }, +], +``` + +Operations represent actions that can be performed on relationships and attributes, such as adding or deleting relationships when certain events occur. + +Let's say user:564 creates an organization:789 in your application. According to your authorization logic, this will result in the creation of several authorization data, including relational tuples and attributes, respectively. + +- organization:789#admin@user:564 +- 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. + +An example request of [RunBundle](./api-overview/data/run-bundle.md) for this scenario: + +```json +POST /bundle +BODY +{ + "name": "project_created", + "arguments": { + "creatorID": "564", + "organizationID": "789", + } +} +``` + +This will result in the creation of the following data in Permify: + +- organization:789#admin@user:564 +- organization:789#manager@user:564 +- organization:789$public|boolean:false + +## Endpoints + +- [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 000000000..c3e7f79a9 --- /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 000000000..d0a6f57f6 --- /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 000000000..c070820b9 --- /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 000000000..316d1e632 --- /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 000000000..3b095823e --- /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/api-reference/data/write-data.mdx b/docs/api-reference/data/write-data.mdx new file mode 100644 index 000000000..53b125265 --- /dev/null +++ b/docs/api-reference/data/write-data.mdx @@ -0,0 +1,447 @@ +--- +title: Write Authorization Data +openapi: post /v1/tenants/{tenant_id}/data/write +--- + +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 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. + + +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 +``` + +## 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) + +### 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 { + 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": "" + } + } + ] +}' +``` + + + + +### 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`. + + +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 + } + } + ] +} +}' +``` + + + + + +**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 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, + +- `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 + } + } + ] +} +}' +``` + + + + +### 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. + +### 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 000000000..b511a3fae --- /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/api-reference/introduction.mdx b/docs/api-reference/introduction.mdx new file mode 100644 index 000000000..3bb962c00 --- /dev/null +++ b/docs/api-reference/introduction.mdx @@ -0,0 +1,108 @@ +--- +title: 'Introduction' +description: 'Example section for showcasing API endpoints' +--- + +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/check-api +[DataService]: ./data/write-data +[SchemaService]: ./schema/write-schema +[TenancyService]: ./tenancy/create-tenant +[go]: https://github.com/Permify/permify-go +[nodeJS]: https://github.com/Permify/permify-node + + + + +

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](./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](../../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: + +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/api-reference/openapi.json b/docs/api-reference/openapi.json new file mode 100644 index 000000000..941caf0a4 --- /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/api-reference/openapi2.json b/docs/api-reference/openapi2.json new file mode 100644 index 000000000..b1509be04 --- /dev/null +++ b/docs/api-reference/openapi2.json @@ -0,0 +1,195 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI Plant Store", + "description": "A sample API that uses a plant store as an example to demonstrate features in the OpenAPI specification", + "license": { + "name": "MIT" + }, + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://sandbox.mintlify.com" + } + ], + "security": [ + { + "bearerAuth": [] + } + ], + "paths": { + "/plants": { + "get": { + "description": "Returns all plants from the system that the user has access to", + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "The maximum number of results to return", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Plant response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Plant" + } + } + } + } + }, + "400": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "description": "Creates a new plant in the store", + "requestBody": { + "description": "Plant to add to the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewPlant" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "plant response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Plant" + } + } + } + }, + "400": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/plants/{id}": { + "delete": { + "description": "Deletes a single plant based on the ID supplied", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of plant to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "204": { + "description": "Plant deleted", + "content": {} + }, + "400": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Plant": { + "required": [ + "name" + ], + "type": "object", + "properties": { + "name": { + "description": "The name of the plant", + "type": "string" + }, + "tag": { + "description": "Tag to specify the type", + "type": "string" + } + } + }, + "NewPlant": { + "allOf": [ + { + "$ref": "#/components/schemas/Plant" + }, + { + "required": [ + "id" + ], + "type": "object", + "properties": { + "id": { + "description": "Identification number of the plant", + "type": "integer", + "format": "int64" + } + } + } + ] + }, + "Error": { + "required": [ + "error", + "message" + ], + "type": "object", + "properties": { + "error": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + }, + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer" + } + } + } +} \ No newline at end of file diff --git a/docs/api-reference/permission/check-api.mdx b/docs/api-reference/permission/check-api.mdx new file mode 100644 index 000000000..8d29b2b92 --- /dev/null +++ b/docs/api-reference/permission/check-api.mdx @@ -0,0 +1,176 @@ +--- +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, 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 + +## Content + +- [Example Check Request](#example-check-request) +- [How Access Decisions Evaluated?](#how-access-decisions-evaluated) +- [Latency & Performance](#latency-and-performance) +- [Parameters & Properties](#parameters-and-properties) + +## Example Check Request + +```javascript +POST /v1/permissions/check +``` + + + + +```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": "" + }, +}' +``` + + + +## 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](../../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 000000000..e3fbf5382 --- /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 000000000..513136437 --- /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 000000000..82decbf68 --- /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 000000000..aa81a943a --- /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 000000000..48d6b1ae7 --- /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 000000000..85c5af9cf --- /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 000000000..3a5ea98cd --- /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/api-reference/schema/write-schema.mdx b/docs/api-reference/schema/write-schema.mdx new file mode 100644 index 000000000..cdac796ed --- /dev/null +++ b/docs/api-reference/schema/write-schema.mdx @@ -0,0 +1,25 @@ +--- +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"***. + + +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. + +### 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) + +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 000000000..08817c23b --- /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 000000000..611625ad0 --- /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 000000000..f3355b677 --- /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 000000000..b77dfbc9a --- /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/development.mdx b/docs/development.mdx new file mode 100644 index 000000000..878300893 --- /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/favicon.svg b/docs/favicon.svg new file mode 100644 index 000000000..0f54248f2 --- /dev/null +++ b/docs/favicon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/docs/getting-started/enforcement.mdx b/docs/getting-started/enforcement.mdx new file mode 100644 index 000000000..2e6c63626 --- /dev/null +++ b/docs/getting-started/enforcement.mdx @@ -0,0 +1,108 @@ +--- +sidebar_position: 4 +icon: 'plug' +title: '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-reference/permission/check-api +[DataService]: ../../api-reference/data/write-data +[SchemaService]: ../../api-reference/schema/write-schema +[TenancyService]: ../../api-reference/tenancy/create-tenant +[go]: https://github.com/Permify/permify-go +[nodeJS]: https://github.com/Permify/permify-node + + + + +

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-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](../../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: + +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/getting-started/examples/facebook-groups.mdx b/docs/getting-started/examples/facebook-groups.mdx new file mode 100644 index 000000000..61fe94e11 --- /dev/null +++ b/docs/getting-started/examples/facebook-groups.mdx @@ -0,0 +1,536 @@ +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. + + +```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. + + + + ```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/getting-started/examples/google-docs.mdx b/docs/getting-started/examples/google-docs.mdx new file mode 100644 index 000000000..12db3e002 --- /dev/null +++ b/docs/getting-started/examples/google-docs.mdx @@ -0,0 +1,330 @@ +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. + + + ```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. + + + + ```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. + + + + ```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/getting-started/examples/instagram.mdx b/docs/getting-started/examples/instagram.mdx new file mode 100644 index 000000000..5353424f7 --- /dev/null +++ b/docs/getting-started/examples/instagram.mdx @@ -0,0 +1,324 @@ +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 + + + + +```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'`. + + + + ```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'`. + + + + +```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'`. + + + + ```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 + + + ```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'`. + + + +```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'`. + + + + ```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 + + +```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'`. + + + + ```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/getting-started/examples/intro.mdx b/docs/getting-started/examples/intro.mdx new file mode 100644 index 000000000..2b76a3c6e --- /dev/null +++ b/docs/getting-started/examples/intro.mdx @@ -0,0 +1,17 @@ +--- +id: examples +title: Introduction +sidebar_label: Real World 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]:./google-docs +[Facebook Groups]:./facebook-groups +[Notion]:./notion +[Instagram]:./instagram +[Mercury]:./mercury \ No newline at end of file diff --git a/docs/getting-started/examples/mercury.mdx b/docs/getting-started/examples/mercury.mdx new file mode 100644 index 000000000..85b88b134 --- /dev/null +++ b/docs/getting-started/examples/mercury.mdx @@ -0,0 +1,157 @@ +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/getting-started/examples/notion.mdx b/docs/getting-started/examples/notion.mdx new file mode 100644 index 000000000..d21851cef --- /dev/null +++ b/docs/getting-started/examples/notion.mdx @@ -0,0 +1,539 @@ +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. + + + ```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. + + + + + ```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/getting-started/modeling.mdx b/docs/getting-started/modeling.mdx new file mode 100644 index 000000000..06d459276 --- /dev/null +++ b/docs/getting-started/modeling.mdx @@ -0,0 +1,619 @@ +--- +icon: "code" +title: "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 + +} +``` + + +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`. + + +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 + +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. + +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. + + +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/instagram) 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) 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. + + + *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 +} +``` + + +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 +} +``` + + + ⛔ 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 +} +``` + + + ⛔ 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 +} +``` + + + ⛔ 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) +} +``` + + + ⛔ 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 +[Facebook Groups]: ./examples/facebook-groups +[Notion]: ./examples/notion +[Instagram]: ./examples/instagram +[Mercury]: ./examples/mercury diff --git a/docs/getting-started/quickstart.mdx b/docs/getting-started/quickstart.mdx new file mode 100644 index 000000000..b05c66746 --- /dev/null +++ b/docs/getting-started/quickstart.mdx @@ -0,0 +1,273 @@ +--- +icon: "rocket" +title: Quickstart +--- + +This guide shows you how to set up Permify in your servers and use it across your applications. + +## 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 Schema](#model-your-authorization-with-permify-schema) +3. [Store Authorization Data & Schema](#store-authorization-data) +4. [Perform Access Check](#perform-access-check) + + +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. + + +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]: ../setting-up/installation/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]: ../getting-started/enforcement + + + +## 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"***. + + +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. + + +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 +[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-reference/schema/write-schema) for configuration of your authorization model on Permify authorization service. + + +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 + +After you completed configuration of your authorization model via Permify Schema. Its time to add authorizations data to see Permify in action. + +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:** + +| 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 relationship:** organization:1#admin@user:1 + +**Semantics:** User 1 (Ashley) has admin role on organization 1. + + +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-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` + +| 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-reference/permission/check-api + +## 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/getting-started/sync-data.mdx b/docs/getting-started/sync-data.mdx new file mode 100644 index 000000000..1fde3de0f --- /dev/null +++ b/docs/getting-started/sync-data.mdx @@ -0,0 +1,472 @@ +--- +sidebar_position: 2 +title: Storing Data & Schema +icon: 'database' +--- + +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. + +## 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 [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. + +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](../operations/snap-tokens) + +## 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": "..." + } + } + ] +}' +``` + + + + +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. \ No newline at end of file diff --git a/docs/getting-started/testing.mdx b/docs/getting-started/testing.mdx new file mode 100644 index 000000000..6cad85248 --- /dev/null +++ b/docs/getting-started/testing.mdx @@ -0,0 +1,279 @@ +--- +sidebar_position: 4 +icon: 'vial' +title: '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). + + +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" +``` + + +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` + + +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](../operations/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"] +``` + + +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 + +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 following [FAQS page](../introduction/faqs#how-to-manage-schema-changes). + +## 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/images/checks-passed.png b/docs/images/checks-passed.png new file mode 100644 index 0000000000000000000000000000000000000000..3303c773646ca12fb6852356663540e3ed048115 GIT binary patch literal 160724 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/hero-light.svg b/docs/images/hero-light.svg new file mode 100644 index 000000000..297d68fb9 --- /dev/null +++ b/docs/images/hero-light.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/introduction/authorization-service.mdx b/docs/introduction/authorization-service.mdx new file mode 100644 index 000000000..9daa42474 --- /dev/null +++ b/docs/introduction/authorization-service.mdx @@ -0,0 +1,89 @@ +--- +sidebar_position: 1 +title: 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. + +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. + +This approach works well until you have several applications with many services. Managing multiple authorization systems for each application is not a scalable approach, as you can imagine. + +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-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. + +![authz-service](https://user-images.githubusercontent.com/34595361/196884110-147862c9-3657-4f07-831c-3e0d0e39eccf.png) + +[Permify]: https://github.com/Permify/permify + +## Building an Authorization Service is Hard + +Building a centralized authorization service yourself is a hard process, and there are several reasons for that. + +Although centralizing authorization is good in so many ways it has one big tradeoff. These centralized engines are stateless, meaning they don’t store data. They just behave as an engine to manage functionality such as performing access checks. + +For instance; in order to make an access check and compute a decision, you need to load the authorization data and relations from the database and other services. In this case, querying the data needed for access check evaluation presents a significant downside in terms of performance and scalability. + +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. + +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. + +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. + +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 + + + 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. + +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. + +## 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/introduction/faqs.mdx b/docs/introduction/faqs.mdx new file mode 100644 index 000000000..224ec3042 --- /dev/null +++ b/docs/introduction/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/introduction/intro.mdx b/docs/introduction/intro.mdx new file mode 100644 index 000000000..0616bec0f --- /dev/null +++ b/docs/introduction/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/logo/.DS_Store b/docs/logo/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + + + + + + + + diff --git a/docs/logo/light.svg b/docs/logo/light.svg new file mode 100644 index 000000000..c4158bf80 --- /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 000000000..7335f8fa4 --- /dev/null +++ b/docs/mint.json @@ -0,0 +1,181 @@ +{ + "$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" + } + ], + "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": [ + "introduction/intro", + "introduction/authorization-service", + "introduction/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/observability", + "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/list-schema", + "api-reference/schema/read-schema", + "api-reference/schema/write-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/operations/bundle.mdx b/docs/operations/bundle.mdx new file mode 100644 index 000000000..fd23cb773 --- /dev/null +++ b/docs/operations/bundle.mdx @@ -0,0 +1,82 @@ +--- +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-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. + +We believe this functionality will streamline managing authorization data as well as managing this in a central place increase visibility around certain actions/triggers that end up with data creation. + +## How Bundles Works + +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-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: + +```json +"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", + ], + }, + ], + }, +], +``` + +Operations represent actions that can be performed on relationships and attributes, such as adding or deleting relationships when certain events occur. + +Let's say user:564 creates an organization:789 in your application. According to your authorization logic, this will result in the creation of several authorization data, including relational tuples and attributes, respectively. + +- organization:789#admin@user:564 +- organization:789#manager@user:564 +- organization:789$public|boolean:false + +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-reference/data/run-bundle) for this scenario: + +```json +POST /bundle +BODY +{ + "name": "project_created", + "arguments": { + "creatorID": "564", + "organizationID": "789", + } +} +``` + +This will result in the creation of the following data in Permify: + +- organization:789#admin@user:564 +- organization:789#manager@user:564 +- organization:789$public|boolean:false + +## Endpoints + +- [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/operations/cache.mdx b/docs/operations/cache.mdx new file mode 100644 index 000000000..f97af9265 --- /dev/null +++ b/docs/operations/cache.mdx @@ -0,0 +1,98 @@ +--- +icon: hard-drive +title: 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) + + + 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 +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/operations/contextual-tuples.mdx b/docs/operations/contextual-tuples.mdx new file mode 100644 index 000000000..42ed74ecf --- /dev/null +++ b/docs/operations/contextual-tuples.mdx @@ -0,0 +1,193 @@ +--- +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. + +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", + } + } +}' +``` + + + + + + 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/operations/observability.mdx b/docs/operations/observability.mdx new file mode 100644 index 000000000..46224c7ac --- /dev/null +++ b/docs/operations/observability.mdx @@ -0,0 +1,58 @@ +--- +icon: magnifying-glass +title: Observability +--- + +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/operations/snap-tokens.mdx b/docs/operations/snap-tokens.mdx new file mode 100644 index 000000000..b4ed3e7e8 --- /dev/null +++ b/docs/operations/snap-tokens.mdx @@ -0,0 +1,61 @@ +--- +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. + +## 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-reference/data/write-data +[Check API]: ../../api-reference/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 Mechanisms](./cache) \ No newline at end of file diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx new file mode 100644 index 000000000..d7f348678 --- /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/setting-up/configuration.mdx b/docs/setting-up/configuration.mdx new file mode 100644 index 000000000..d577170d6 --- /dev/null +++ b/docs/setting-up/configuration.mdx @@ -0,0 +1,549 @@ +--- +title: Configuration +icon: gear +--- + +Permify offers various options for configuring your Permify 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. + +### 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, +# 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" + +``` + +## Configuration Glossary + + + +#### 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 path | + +#### 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 | + + + + + +#### 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 | + + + + + +#### Definition + +You can choose to authenticate users to interact with Permify API. + +There are 2 authentication method you can choose: + +* Pre Shared Keys +* OpenID Connect + +#### Pre Shared Keys + +On this method, you must provide a pre shared keys in order to identify yourself. + +#### Structure + +``` +├── authn +| ├── method +| ├── enabled +| ├── preshared +| ├── 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 +| ├── oidc +| ├── issuer +| ├── audience +``` + +#### Glossary + +| Required | Argument | Default | Description | +|----------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [x] | method | - | Authentication method can be either `oidc` or `preshared`. | +| [ ] | enabled | false | switch option authentication config | +| [x] | audience | - | The audience identifies the intended recipients of the token, typically the API or resource server. It ensures tokens are used only by the authorized party. | +| [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-audience | PERMIFY_AUTHN_OIDC_AUDIENCE | string | + + + + + +#### 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 | + + + + + +#### 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 | + + + + + +#### 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 | + + + + + +#### 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 | + + + + + +#### 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 | + + + + + +#### 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/setting-up/installation/.DS_Store b/docs/setting-up/installation/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + +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](#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) + +## 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/setting-up/installation/brew.mdx b/docs/setting-up/installation/brew.mdx new file mode 100644 index 000000000..de2cb0c52 --- /dev/null +++ b/docs/setting-up/installation/brew.mdx @@ -0,0 +1,85 @@ +--- +title: Install with Brew +--- + +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 +``` + + +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]: ../../getting-started/enforcement + + + +### 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/setting-up/installation/container.mdx b/docs/setting-up/installation/container.mdx new file mode 100644 index 000000000..7cbea8fab --- /dev/null +++ b/docs/setting-up/installation/container.mdx @@ -0,0 +1,74 @@ +--- +title: 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](../configuration), where you can configure the Permify Server as well as define the ***database*** to store your authorization related data in. + + +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 +``` + + +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]: ../../getting-started/enforcement + + + +### 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/setting-up/installation/google.mdx b/docs/setting-up/installation/google.mdx new file mode 100644 index 000000000..deb30dc91 --- /dev/null +++ b/docs/setting-up/installation/google.mdx @@ -0,0 +1,271 @@ +--- +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/setting-up/installation/helm.mdx b/docs/setting-up/installation/helm.mdx new file mode 100644 index 000000000..421b366bc --- /dev/null +++ b/docs/setting-up/installation/helm.mdx @@ -0,0 +1,119 @@ +--- +title: Deploying Permify with Helm Charts +--- + +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/) + +## Helm Charts for Permify + +Permify provides Helm Charts to facilitate the deployment and management of Permify in Kubernetes environments. Helm Charts encapsulate all the necessary Kubernetes resources and configurations required to run Permify, making it easy to deploy and maintain.([helm-permify-github](https://github.com/Permify/helm-charts)) + +## Helm installation + +### Prerequisite + +Installing the Helm Chart pretty easy but there is a pre-requisite of setting up Kubernetes Cluster. + +If you do not have a Kubernetes cluster you can choose any of the four below options. + +**1. [EKS-Amazon Elastic k8s service](https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html)** + +**2. [GKE-Google k8s engine](https://cloud.google.com/kubernetes-engine)** + +**3. [AKS-Azure kubernetes Service](https://azure.microsoft.com/en-in/products/kubernetes-service)** + +**4. [microk8s](https://microk8s.io/#install-microk8s)** + +### 1.1: Install Helm Chart Using Script + +If you like doing everything from scratch then I would suggest you to install the Helm Chart Using script. + +Run the following scripts - + +```bash +curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 +``` + +```bash +chmod 700 get_helm.sh +``` + +```bash +./get_helm.sh +``` + +You can verify the installation by running the command + +```bash +helm version +``` + +If helm is installed the terminal will provide this as output + +```bash +WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /home/vagrant/.kube/config +version.BuildInfo{Version:"v3.4.0", GitCommit:"7090a89efc8a18f3d8178bf47d2462450349a004", GitTreeState:"clean", GoVersion:"go1.14.10"} +``` + +### 1.2: Install Helm Chart with package Manager + +If you like package manager then you use the following install command based on your preference - + +**Homebrew** + +```bash +brew install helm +``` + +**Chocolatey** + +```bash +choco install kubernetes-helm +``` + +**Scoop** + +``` +scoop install helm +``` + +**Snap** + +```bash +sudo snap install helm --classic +``` + +If helm is installed the terminal will provide this as output + +```bash +WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /home/vagrant/.kube/config +version.BuildInfo{Version:"v3.4.0", GitCommit:"7090a89efc8a18f3d8178bf47d2462450349a004", GitTreeState:"clean", GoVersion:"go1.14.10"} +``` + +## Adding the Permify Helm Charts Repository + +To use Permify Helm Charts, you need to add the Permify Helm Charts repository to Helm. Follow these steps: + +**1.** Open your terminal. + +**2.** Run the following command to add the Permify Helm Charts repository: + +```bash +$ helm repo add permify https://permify.github.io/helm-charts +``` + +**3.** After adding the Permify Helm Charts repository, you can search for available charts using the following command: + +```bash +$ helm search repo permify +``` + +**Installing Permify using Helm Charts** + +Once you've added the Permify Helm Charts repository, you can install Permify using Helm + +```bash +helm install permify permify/permify +``` \ No newline at end of file diff --git a/docs/setting-up/installation/intro.mdx b/docs/setting-up/installation/intro.mdx new file mode 100644 index 000000000..5b48761f3 --- /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/setting-up/installation/kubernetes.mdx b/docs/setting-up/installation/kubernetes.mdx new file mode 100644 index 000000000..fffa5c4b8 --- /dev/null +++ b/docs/setting-up/installation/kubernetes.mdx @@ -0,0 +1,171 @@ +--- +title: 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-and-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/snippets/snippet-intro.mdx b/docs/snippets/snippet-intro.mdx new file mode 100644 index 000000000..c57e7c756 --- /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/use-cases/.DS_Store b/docs/use-cases/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 +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 +} +``` +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 +} +``` + +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 +} +``` +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) +} +``` +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/use-cases/custom-roles.mdx b/docs/use-cases/custom-roles.mdx new file mode 100644 index 000000000..95a620df0 --- /dev/null +++ b/docs/use-cases/custom-roles.mdx @@ -0,0 +1,75 @@ +--- +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. + +[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/use-cases/multi-tenancy.mdx b/docs/use-cases/multi-tenancy.mdx new file mode 100644 index 000000000..33adcccf5 --- /dev/null +++ b/docs/use-cases/multi-tenancy.mdx @@ -0,0 +1,137 @@ +--- +title: Multi Tenancy +--- + +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 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. + +### Tenancy Based APIs + +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 { + 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](../getting-started/enforcement). + +### Tenancy Service + +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 + +#### Tenant Table + +A tenants table has been added to the Permissions database to store tenants' details. + +``` +tables +├── migrations +├── relation_tuples +├── schema_definitions +├── tenants +├── transactions +``` + +#### Tenant ID Column + +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. + +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/use-cases/rbac.mdx b/docs/use-cases/rbac.mdx new file mode 100644 index 000000000..9835ac402 --- /dev/null +++ b/docs/use-cases/rbac.mdx @@ -0,0 +1,126 @@ +--- +title: Role Based Access Control (RBAC) +--- + +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 + +## 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/use-cases/rebac.mdx b/docs/use-cases/rebac.mdx new file mode 100644 index 000000000..f33c98b6d --- /dev/null +++ b/docs/use-cases/rebac.mdx @@ -0,0 +1,423 @@ +--- +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. + +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-and-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 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, + +```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 From 3eb6b8bb6b201ef660f9ef8f769d1e9b05c79ffc Mon Sep 17 00:00:00 2001 From: ahmethakanbesel Date: Tue, 19 Mar 2024 14:46:32 +0300 Subject: [PATCH 51/70] authn/oidc: update a comment line --- internal/authn/oidc/authn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/authn/oidc/authn.go b/internal/authn/oidc/authn.go index 9e539f801..fbc993f5d 100644 --- a/internal/authn/oidc/authn.go +++ b/internal/authn/oidc/authn.go @@ -103,7 +103,7 @@ func (oidc *Authn) Authenticate(requestContext context.Context) error { token, err := jwtParser.Parse(authHeader, func(token *jwt.Token) (any, error) { // If a presented token's KID is not found in the existing headers, initiate a JWKs fetch and validate the token. if _, ok := token.Header["kid"].(string); !ok { - // Whem KID is absent in the header and it has been less than defaultKeyRefreshInterval since the last JWKs retrieval attempt, reject the token. + // Whem KID is absent in the header and it has been less than the interval since the last JWKs retrieval attempt, reject the token. if !oidc.refreshUnknownKID && time.Since(oidc.lastKeyFetch) < oidc.keyRefreshInterval { return nil, errors.New(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()) } From 92540bb9fbd4821e1ab644830935b2096e66a91d Mon Sep 17 00:00:00 2001 From: EgeAytin Date: Tue, 19 Mar 2024 15:24:00 +0300 Subject: [PATCH 52/70] docs: added python & js SDKs to certain pages --- docs/api-reference/data/write-data.mdx | 88 +++++++++++++++++++++ docs/api-reference/introduction.mdx | 11 ++- docs/api-reference/permission/check-api.mdx | 36 ++++++++- docs/getting-started/enforcement.mdx | 21 +++-- docs/mint.json | 4 +- docs/operations/contextual-tuples.mdx | 49 ++++++++++++ docs/use-cases/multi-tenancy.mdx | 43 ++++++++++ 7 files changed, 238 insertions(+), 14 deletions(-) diff --git a/docs/api-reference/data/write-data.mdx b/docs/api-reference/data/write-data.mdx index 53b125265..7be26346a 100644 --- a/docs/api-reference/data/write-data.mdx +++ b/docs/api-reference/data/write-data.mdx @@ -89,6 +89,28 @@ 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", + }, + }] + ) +``` @@ -190,6 +212,33 @@ 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, + }] + ) +``` @@ -329,6 +378,45 @@ 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, + }] + ) + diff --git a/docs/api-reference/introduction.mdx b/docs/api-reference/introduction.mdx index 3bb962c00..d6773ccb8 100644 --- a/docs/api-reference/introduction.mdx +++ b/docs/api-reference/introduction.mdx @@ -12,14 +12,19 @@ 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/). +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 -[go]: https://github.com/Permify/permify-go -[nodeJS]: https://github.com/Permify/permify-node + +**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)
- + +```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' \ diff --git a/docs/getting-started/enforcement.mdx b/docs/getting-started/enforcement.mdx index 2e6c63626..ece71dfad 100644 --- a/docs/getting-started/enforcement.mdx +++ b/docs/getting-started/enforcement.mdx @@ -13,14 +13,19 @@ 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-reference/permission/check-api -[DataService]: ../../api-reference/data/write-data -[SchemaService]: ../../api-reference/schema/write-schema -[TenancyService]: ../../api-reference/tenancy/create-tenant -[go]: https://github.com/Permify/permify-go -[nodeJS]: https://github.com/Permify/permify-node +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)
+ + +```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}") +``` + diff --git a/docs/use-cases/multi-tenancy.mdx b/docs/use-cases/multi-tenancy.mdx index 33adcccf5..4f3191137 100644 --- a/docs/use-cases/multi-tenancy.mdx +++ b/docs/use-cases/multi-tenancy.mdx @@ -72,6 +72,49 @@ 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}") +``` + From 9432ead32beca8c6f7ac834c491aeb1e7d6b4d9a Mon Sep 17 00:00:00 2001 From: EgeAytin Date: Tue, 19 Mar 2024 20:20:00 +0300 Subject: [PATCH 53/70] docs: changed the slugs for seo compatiblity --- docs/.DS_Store | Bin 10244 -> 8196 bytes docs/mint.json | 8 ++++---- .../{observability.mdx => tracing.mdx} | 0 .../authorization-service.mdx | 0 .../faqs.mdx | 0 .../intro.mdx | 0 6 files changed, 4 insertions(+), 4 deletions(-) rename docs/operations/{observability.mdx => tracing.mdx} (100%) rename docs/{introduction => permify-overview}/authorization-service.mdx (100%) rename docs/{introduction => permify-overview}/faqs.mdx (100%) rename docs/{introduction => permify-overview}/intro.mdx (100%) diff --git a/docs/.DS_Store b/docs/.DS_Store index 919a104a48c5440d55fa8835e0a30e26c58d6f21..5832fb72242b739aa8b3f327b653cd0c9ac3988d 100644 GIT binary patch delta 106 zcmZn(XmOBWU|?W$DortDU;r^WfEYvza8E20o2aMAsIoC&H$S7wYoS vieTIQkWV)riwDIZ#A+vy1Ry zmW>UD?1Icdvw=W?8%Vf=;YNpk=I diff --git a/docs/mint.json b/docs/mint.json index 9a9e410af..526a0692c 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -48,9 +48,9 @@ { "group": "Introduction", "pages": [ - "introduction/intro", - "introduction/authorization-service", - "introduction/faqs" + "permify-overview/intro", + "permify-overview/authorization-service", + "permify-overview/faqs" ] }, { @@ -101,7 +101,7 @@ "operations/bundle", "operations/cache", "operations/contextual-tuples", - "operations/observability", + "operations/tracing", "operations/snap-tokens" ] }, diff --git a/docs/operations/observability.mdx b/docs/operations/tracing.mdx similarity index 100% rename from docs/operations/observability.mdx rename to docs/operations/tracing.mdx diff --git a/docs/introduction/authorization-service.mdx b/docs/permify-overview/authorization-service.mdx similarity index 100% rename from docs/introduction/authorization-service.mdx rename to docs/permify-overview/authorization-service.mdx diff --git a/docs/introduction/faqs.mdx b/docs/permify-overview/faqs.mdx similarity index 100% rename from docs/introduction/faqs.mdx rename to docs/permify-overview/faqs.mdx diff --git a/docs/introduction/intro.mdx b/docs/permify-overview/intro.mdx similarity index 100% rename from docs/introduction/intro.mdx rename to docs/permify-overview/intro.mdx From 78b9a975b0a60c93a7afaa86a932b1ced0f3d1ae Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Tue, 19 Mar 2024 21:25:28 +0300 Subject: [PATCH 54/70] fix: update path in buf.gen.yaml for OpenAPI reference generation --- buf.gen.yaml | 2 +- docs/api-reference/apidocs.swagger.json | 3337 +++++++++++++++++++++++ docs/api-reference/openapi2.json | 195 -- 3 files changed, 3338 insertions(+), 196 deletions(-) create mode 100644 docs/api-reference/apidocs.swagger.json delete mode 100644 docs/api-reference/openapi2.json diff --git a/buf.gen.yaml b/buf.gen.yaml index 62b9dd704..defc4465d 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/api-reference/apidocs.swagger.json b/docs/api-reference/apidocs.swagger.json new file mode 100644 index 000000000..d90386e76 --- /dev/null +++ b/docs/api-reference/apidocs.swagger.json @@ -0,0 +1,3337 @@ +{ + "swagger": "2.0", + "info": { + "title": "Permify API", + "description": "Permify is an open source authorization service for creating fine-grained and scalable authorization systems.", + "version": "v0.7.7", + "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" + } + }, + "tags": [ + { + "name": "Permission" + }, + { + "name": "Watch" + }, + { + "name": "Schema" + }, + { + "name": "Data" + }, + { + "name": "Bundle" + }, + { + "name": "Tenancy" + } + ], + "schemes": [ + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v1/tenants/create": { + "post": { + "summary": "create tenant", + "operationId": "tenants.create", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/TenantCreateResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "parameters": [ + { + "name": "body", + "description": "TenantCreateRequest is the message used for the request to create a tenant.", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/TenantCreateRequest" + } + } + ], + "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}'" + } + ] + } + }, + "/v1/tenants/list": { + "post": { + "summary": "list tenants", + "operationId": "tenants.list", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/TenantListResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "parameters": [ + { + "name": "body", + "description": "TenantListRequest is the message used for the request to list all tenants.", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/TenantListRequest" + } + } + ], + "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}'" + } + ] + } + }, + "/v1/tenants/{id}": { + "delete": { + "summary": "delete tenant", + "operationId": "tenants.delete", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/TenantDeleteResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "parameters": [ + { + "name": "id", + "description": "id is the unique identifier of the tenant to be deleted.", + "in": "path", + "required": true, + "type": "string" + } + ], + "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'" + } + ] + } + }, + "/v1/tenants/{tenant_id}/bundle/delete": { + "post": { + "summary": "delete bundle", + "operationId": "bundle.delete", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/BundleDeleteResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "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." + } + } + ], + "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}'" + } + ] + } + }, + "/v1/tenants/{tenant_id}/bundle/read": { + "post": { + "summary": "read bundle", + "operationId": "bundle.read", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/BundleReadResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + ], + "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}'" + } + ] + } + }, + "/v1/tenants/{tenant_id}/bundle/write": { + "post": { + "summary": "write bundle", + "operationId": "bundle.write", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/BundleWriteResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "bundles": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/DataBundle" + }, + "description": "Contains the bundle data to be written." + } + }, + "description": "BundleWriteRequest is used to request the writing of a bundle.\nIt contains the tenant_id to identify the tenant and the Bundles object." + } + } + ], + "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 attributes", + "operationId": "data.attributes.read", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/AttributeReadResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/definitions/AttributeReadRequestMetadata", + "description": "metadata holds additional information related to the request." + }, + "filter": { + "$ref": "#/definitions/AttributeFilter", + "description": "filter specifies the criteria used to select the attributes that should be returned." + }, + "page_size": { + "type": "integer", + "format": "int64", + "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." + }, + "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." + } + } + ], + "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}'" + } + ] + } + }, + "/v1/tenants/{tenant_id}/data/delete": { + "post": { + "summary": "delete data", + "operationId": "data.delete", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/DataDeleteResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "tuple_filter": { + "$ref": "#/definitions/TupleFilter", + "description": "tuple_filter specifies the criteria used to select the tuples that should be deleted." + }, + "attribute_filter": { + "$ref": "#/definitions/AttributeFilter", + "description": "attribute_filter specifies the criteria used to select the attributes that should be deleted." + } + }, + "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." + } + } + ], + "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 relationships", + "operationId": "data.relationships.read", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/RelationshipReadResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/definitions/RelationshipReadRequestMetadata", + "description": "metadata holds additional data related to the request." + }, + "filter": { + "$ref": "#/definitions/TupleFilter", + "description": "filter is used to specify criteria for the data that needs to be read." + }, + "page_size": { + "type": "integer", + "format": "int64", + "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." + }, + "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." + } + } + ], + "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}'" + } + ] + } + }, + "/v1/tenants/{tenant_id}/data/run-bundle": { + "post": { + "summary": "run bundle", + "operationId": "bundle.run", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/BundleRunResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "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." + } + } + ], + "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": "write data", + "operationId": "data.write", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/DataWriteResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/definitions/DataWriteRequestMetadata", + "description": "metadata holds additional data related to the request." + }, + "tuples": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/Tuple" + }, + "description": "tuples contains the list of tuples (entity-relation-entity triples) that need to be written." + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/Attribute" + }, + "description": "attributes contains the list of attributes (entity-attribute-value triples) that need to be written." + } + }, + "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." + } + } + ], + "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": "check api", + "operationId": "permissions.check", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/PermissionCheckResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/definitions/PermissionCheckRequestMetadata", + "description": "Metadata associated with this request, required." + }, + "entity": { + "$ref": "#/definitions/Entity", + "example": "repository:1", + "description": "Entity on which the permission needs to be checked, required." + }, + "permission": { + "type": "string", + "description": "The action the user wants to perform on the resource" + }, + "subject": { + "$ref": "#/definitions/Subject", + "description": "Subject for which the permission needs to be checked, required." + }, + "context": { + "$ref": "#/definitions/Context", + "description": "Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../operations/contextual-tuples)" + }, + "arguments": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/Argument" + }, + "description": "Additional arguments associated with this request." + } + }, + "description": "PermissionCheckRequest is the request message for the Check method in the Permission service." + } + } + ], + "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 api", + "operationId": "permissions.expand", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/PermissionExpandResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/definitions/PermissionExpandRequestMetadata", + "description": "Metadata associated with this request, required." + }, + "entity": { + "$ref": "#/definitions/Entity", + "description": "Entity on which the permission needs to be expanded, required." + }, + "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": "#/definitions/Context", + "description": "Context associated with this request." + }, + "arguments": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/Argument" + }, + "description": "Additional arguments associated with this request." + } + }, + "description": "PermissionExpandRequest is the request message for the Expand method in the Permission service." + } + } + ], + "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": "lookup entity", + "operationId": "permissions.lookupEntity", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/PermissionLookupEntityResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/definitions/PermissionLookupEntityRequestMetadata", + "description": "Metadata associated with this request, required." + }, + "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": "#/definitions/Subject", + "description": "Subject for which to check the permission, required." + }, + "context": { + "$ref": "#/definitions/Context", + "description": "Context associated with this request." + } + }, + "description": "PermissionLookupEntityRequest is the request message for the LookupEntity method in the Permission service." + } + } + ], + "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": "lookup entity stream", + "operationId": "permissions.lookupEntityStream", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/PermissionLookupEntityStreamResponse" + }, + "error": { + "$ref": "#/definitions/Status" + } + }, + "title": "Stream result of PermissionLookupEntityStreamResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/definitions/PermissionLookupEntityRequestMetadata", + "description": "Metadata associated with this request, required." + }, + "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": "#/definitions/Subject", + "description": "Subject for which to check the permission, required." + }, + "context": { + "$ref": "#/definitions/Context", + "description": "Context associated with this request." + } + }, + "description": "PermissionLookupEntityRequest is the request message for the LookupEntity method in the Permission service." + } + } + ], + "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": "lookup-subject", + "operationId": "permissions.lookupSubject", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/PermissionLookupSubjectResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/definitions/PermissionLookupSubjectRequestMetadata", + "description": "Metadata associated with this request, required." + }, + "entity": { + "$ref": "#/definitions/Entity", + "description": "Entity for which to check the permission, required." + }, + "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": "#/definitions/RelationReference", + "description": "Reference to the subject to lookup." + }, + "context": { + "$ref": "#/definitions/Context", + "description": "Context associated with this request." + } + }, + "description": "PermissionLookupSubjectRequest is the request message for the LookupSubject method in the Permission service." + } + } + ], + "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": "subject permission", + "operationId": "permissions.subjectPermission", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/PermissionSubjectPermissionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/definitions/PermissionSubjectPermissionRequestMetadata", + "description": "Metadata associated with this request, required." + }, + "entity": { + "$ref": "#/definitions/Entity", + "description": "Entity for which to check the permission, required." + }, + "subject": { + "$ref": "#/definitions/Subject", + "description": "Subject for which to check the permission, required." + }, + "context": { + "$ref": "#/definitions/Context", + "description": "Context associated with this request." + } + }, + "description": "PermissionSubjectPermissionRequest is the request message for the SubjectPermission method in the Permission service." + } + } + ], + "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}'" + } + ] + } + }, + "/v1/tenants/{tenant_id}/relationships/delete": { + "post": { + "summary": "delete relationships", + "operationId": "relationships.delete", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/RelationshipDeleteResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "filter": { + "$ref": "#/definitions/TupleFilter" + } + }, + "title": "RelationshipDeleteRequest" + } + } + ], + "tags": [ + "Data" + ] + } + }, + "/v1/tenants/{tenant_id}/relationships/write": { + "post": { + "summary": "write relationships", + "operationId": "relationships.write", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/RelationshipWriteResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/definitions/RelationshipWriteRequestMetadata", + "description": "Metadata for the request. It's required." + }, + "tuples": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/Tuple" + }, + "description": "List of tuples for the request. Must have between 1 and 100 items." + } + }, + "description": "Represents a request to write relationship data." + } + } + ], + "tags": [ + "Data" + ] + } + }, + "/v1/tenants/{tenant_id}/schemas/list": { + "post": { + "summary": "list schema", + "operationId": "schemas.list", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/SchemaListResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "page_size": { + "type": "integer", + "format": "int64", + "description": "page_size is the number of tenants to be returned in the response.\nThe value should be between 1 and 100." + }, + "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." + } + } + ], + "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 schema", + "operationId": "schemas.read", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/SchemaReadResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "metadata": { + "$ref": "#/definitions/SchemaReadRequestMetadata", + "description": "metadata is the additional information needed for the Read request." + } + }, + "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." + } + } + ], + "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 schema", + "operationId": "schemas.write", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/SchemaWriteResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "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." + } + } + ], + "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": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/WatchResponse" + }, + "error": { + "$ref": "#/definitions/Status" + } + }, + "title": "Stream result of WatchResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Status" + } + } + }, + "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" + }, + { + "name": "body", + "in": "body", + "required": true, + "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." + } + } + ], + "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" + } + ] + } + } + }, + "definitions": { + "AbstractType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The fully qualified name of this abstract type." + }, + "parameterTypes": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1alpha1.Type" + }, + "description": "Parameter types for this abstract 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": {}, + "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(\u0026foo)) {\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 := \u0026pb.Foo{...}\n any, err := anypb.New(foo)\n if err != nil {\n ...\n }\n ...\n foo := \u0026pb.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\": \u003cstring\u003e,\n \"lastName\": \u003cstring\u003e\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": "#/definitions/ComputedAttribute" + }, + "contextAttribute": { + "$ref": "#/definitions/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": "#/definitions/Entity" + }, + "attribute": { + "type": "string", + "title": "Name of the attribute" + }, + "value": { + "$ref": "#/definitions/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": "#/definitions/AttributeType", + "description": "The type of the attribute." + } + }, + "description": "The AttributeDefinition message provides detailed information about a specific attribute." + }, + "AttributeFilter": { + "type": "object", + "properties": { + "entity": { + "$ref": "#/definitions/EntityFilter" + }, + "attributes": { + "type": "array", + "items": { + "type": "string" + }, + "title": "Names of the attributes to be filtered" + } + }, + "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", + "items": { + "type": "object", + "$ref": "#/definitions/Attribute" + }, + "description": "attributes is a list of the attributes retrieved in the read operation." + }, + "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", + "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" + ], + "default": "ATTRIBUTE_TYPE_UNSPECIFIED", + "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." + }, + "BundleDeleteResponse": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "BundleReadResponse": { + "type": "object", + "properties": { + "bundle": { + "$ref": "#/definitions/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", + "items": { + "type": "string" + }, + "description": "Identifier or acknowledgment of the written bundle." + } + }, + "description": "BundleWriteResponse is the response for a BundleWriteRequest.\nIt includes a name which could be used as an identifier or acknowledgment." + }, + "CheckResult": { + "type": "string", + "enum": [ + "CHECK_RESULT_UNSPECIFIED", + "CHECK_RESULT_ALLOWED", + "CHECK_RESULT_DENIED" + ], + "default": "CHECK_RESULT_UNSPECIFIED", + "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)." + }, + "CheckedExpr": { + "type": "object", + "properties": { + "referenceMap": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/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": "#/definitions/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": "#/definitions/SourceInfo", + "description": "The source info derived from input that generated the parsed `expr` and\nany optimizations made during the type-checking pass." + }, + "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": "#/definitions/Expr", + "description": "The checked expression. Semantically equivalent to the parsed `expr`, but\nmay have structural differences." + } + }, + "description": "A CEL expression which has been successfully type checked." + }, + "Child": { + "type": "object", + "properties": { + "leaf": { + "$ref": "#/definitions/Leaf", + "description": "Leaf node in the permission tree." + }, + "rewrite": { + "$ref": "#/definitions/Rewrite", + "description": "Rewrite operation in the permission tree." + } + }, + "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": "#/definitions/Expr", + "description": "The range over which var iterates." + }, + "accuVar": { + "type": "string", + "description": "The name of the variable used for accumulation of the result." + }, + "accuInit": { + "$ref": "#/definitions/Expr", + "description": "The initial value of the accumulator." + }, + "loopCondition": { + "$ref": "#/definitions/Expr", + "description": "An expression which can contain iter_var and accu_var.\n\nReturns false when the result has been computed and may be used as\na hint to short-circuit the remainder of the comprehension." + }, + "loopStep": { + "$ref": "#/definitions/Expr", + "description": "An expression which can contain iter_var and accu_var.\n\nComputes the next value of accu_var." + }, + "result": { + "$ref": "#/definitions/Expr", + "description": "An expression which can contain accu_var.\n\nComputes the result." + } + }, + "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 \u003c 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 \u003e 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": { + "type": "string", + "title": "Name of the computed attribute" + } + }, + "description": "ComputedAttribute defines a computed attribute which includes its name." + }, + "ComputedUserSet": { + "type": "object", + "properties": { + "relation": { + "type": "string", + "title": "Relation name" + } + }, + "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", + "format": "int64", + "description": "int64 value." + }, + "uint64Value": { + "type": "string", + "format": "uint64", + "description": "uint64 value." + }, + "doubleValue": { + "type": "number", + "format": "double", + "description": "double value." + }, + "stringValue": { + "type": "string", + "description": "string value." + }, + "bytesValue": { + "type": "string", + "format": "byte", + "description": "bytes value." + }, + "durationValue": { + "type": "string", + "description": "protobuf.Duration value.\n\nDeprecated: duration is no longer considered a builtin cel type." + }, + "timestampValue": { + "type": "string", + "format": "date-time", + "description": "protobuf.Timestamp value.\n\nDeprecated: timestamp is no longer considered a builtin cel type." + } + }, + "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", + "items": { + "type": "object", + "$ref": "#/definitions/Tuple" + }, + "description": "A repeated field of tuples involved in the operation." + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/Attribute" + }, + "description": "A repeated field of attributes associated with the operation." + }, + "data": { + "type": "object", + "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": { + "type": "string", + "title": "Name of the context attribute" + } + }, + "description": "ContextAttribute defines a context attribute which includes its name." + }, + "CreateList": { + "type": "object", + "properties": { + "elements": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/Expr" + }, + "description": "The elements part of the list." + }, + "optionalIndices": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "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." + } + }, + "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", + "items": { + "type": "object", + "$ref": "#/definitions/Entry" + }, + "description": "The entries in the creation expression." + } + }, + "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", + "items": { + "type": "string" + }, + "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." + }, + "operations": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1.Operation" + }, + "description": "'operations' is a repeated field containing multiple Operation messages.\nEach Operation represents a specific action or set of actions to be performed." + } + }, + "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": "#/definitions/DataChange.Operation", + "description": "The operation type." + }, + "tuple": { + "$ref": "#/definitions/Tuple", + "description": "If the change is a tuple." + }, + "attribute": { + "$ref": "#/definitions/Attribute", + "description": "If the change is an 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", + "enum": [ + "OPERATION_UNSPECIFIED", + "OPERATION_CREATE", + "OPERATION_DELETE" + ], + "default": "OPERATION_UNSPECIFIED", + "description": " - OPERATION_UNSPECIFIED: Default operation, not specified.\n - OPERATION_CREATE: Creation operation.\n - OPERATION_DELETE: Deletion operation." + }, + "DataChanges": { + "type": "object", + "properties": { + "snap_token": { + "type": "string", + "description": "The snapshot token." + }, + "data_changes": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/DataChange" + }, + "description": "The list of data changes." + } + }, + "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": "#/definitions/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": "#/definitions/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": "#/definitions/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": "#/definitions/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", + "enum": [ + "REFERENCE_UNSPECIFIED", + "REFERENCE_RELATION", + "REFERENCE_PERMISSION", + "REFERENCE_ATTRIBUTE" + ], + "default": "REFERENCE_UNSPECIFIED", + "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." + }, + "EntityFilter": { + "type": "object", + "properties": { + "type": { + "type": "string", + "title": "Type of the entity" + }, + "ids": { + "type": "array", + "items": { + "type": "string" + }, + "title": "List of entity IDs" + } + }, + "description": "EntityFilter is used to filter entities based on the type and ids." + }, + "Entry": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "int64", + "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." + }, + "fieldKey": { + "type": "string", + "description": "The field key for a message creator statement." + }, + "mapKey": { + "$ref": "#/definitions/Expr", + "description": "The key expression for a map creation statement." + }, + "value": { + "$ref": "#/definitions/Expr", + "description": "Required. The value assigned to the key.\n\nIf the optional_entry field is true, the expression must resolve to an\noptional-typed value. If the optional value is present, the key will be\nset; however, if the optional value is absent, the key will be unset." + }, + "optionalEntry": { + "type": "boolean", + "description": "Whether the key-value pair is optional." + } + }, + "description": "Represents an entry." + }, + "Expand": { + "type": "object", + "properties": { + "entity": { + "$ref": "#/definitions/Entity", + "description": "entity is the entity for which the hierarchical structure is defined." + }, + "permission": { + "type": "string", + "description": "permission is the permission applied to the entity." + }, + "arguments": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/Argument" + }, + "description": "arguments are the additional information or context used to evaluate permissions." + }, + "expand": { + "$ref": "#/definitions/ExpandTreeNode", + "description": "expand contains another hierarchical structure." + }, + "leaf": { + "$ref": "#/definitions/ExpandLeaf", + "description": "leaf contains a set of subjects." + } + }, + "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": "#/definitions/Subjects", + "description": "subjects are used when the leaf is a set of subjects." + }, + "values": { + "$ref": "#/definitions/Values", + "description": "values are used when the leaf node is a set of values." + }, + "value": { + "$ref": "#/definitions/Any", + "description": "value is used when the leaf node is a single value." + } + }, + "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": "#/definitions/ExpandTreeNode.Operation", + "title": "Operation to be applied on this tree node" + }, + "children": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/Expand" + }, + "title": "The children of this tree node" + } + }, + "description": "ExpandTreeNode represents a node in an expansion tree with a specific operation and its children." + }, + "ExpandTreeNode.Operation": { + "type": "string", + "enum": [ + "OPERATION_UNSPECIFIED", + "OPERATION_UNION", + "OPERATION_INTERSECTION", + "OPERATION_EXCLUSION" + ], + "default": "OPERATION_UNSPECIFIED", + "description": "Operation is an enum representing the type of operation to be applied on the tree node." + }, + "Expr": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "int64", + "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." + }, + "constExpr": { + "$ref": "#/definitions/Constant", + "description": "A literal expression." + }, + "identExpr": { + "$ref": "#/definitions/Ident", + "description": "An identifier expression." + }, + "selectExpr": { + "$ref": "#/definitions/Select", + "description": "A field selection expression, e.g. `request.auth`." + }, + "callExpr": { + "$ref": "#/definitions/Expr.Call", + "description": "A call expression, including calls to predefined functions and operators." + }, + "listExpr": { + "$ref": "#/definitions/CreateList", + "description": "A list creation expression." + }, + "structExpr": { + "$ref": "#/definitions/CreateStruct", + "description": "A map or message creation expression." + }, + "comprehensionExpr": { + "$ref": "#/definitions/Comprehension", + "description": "A comprehension expression." + } + }, + "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": "#/definitions/Expr", + "description": "The target of an method call-style expression. For example, `x` in\n`x.f()`." + }, + "function": { + "type": "string", + "description": "Required. The name of the function or method being called." + }, + "args": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/Expr" + }, + "description": "The arguments." + } + }, + "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": "#/definitions/v1alpha1.Type", + "description": "Result type of the function." + }, + "argTypes": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1alpha1.Type" + }, + "description": "Argument types of the function." + } + }, + "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": "#/definitions/ComputedUserSet", + "description": "A computed set of users." + }, + "tupleToUserSet": { + "$ref": "#/definitions/TupleToUserSet", + "description": "A tuple to user set conversion." + }, + "computedAttribute": { + "$ref": "#/definitions/ComputedAttribute", + "description": "A computed attribute." + }, + "call": { + "$ref": "#/definitions/v1.Call", + "description": "A call to a function or method." + } + }, + "description": "Leaf represents a leaf node in the permission tree." + }, + "ListType": { + "type": "object", + "properties": { + "elemType": { + "$ref": "#/definitions/v1alpha1.Type", + "description": "The element type." + } + }, + "description": "List type with typed elements, e.g. `list\u003cexample.proto.MyMessage\u003e`." + }, + "MapType": { + "type": "object", + "properties": { + "keyType": { + "$ref": "#/definitions/v1alpha1.Type", + "description": "The type of the key." + }, + "valueType": { + "$ref": "#/definitions/v1alpha1.Type", + "description": "The type of the value." + } + }, + "description": "Map type with parameterized key and value types, e.g. `map\u003cstring, int\u003e`." + }, + "NullValue": { + "type": "string", + "enum": [ + "NULL_VALUE" + ], + "default": "NULL_VALUE", + "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." + }, + "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", + "format": "int32", + "description": "Query limit when if recursive database queries got in loop" + } + }, + "description": "PermissionCheckRequestMetadata metadata for the PermissionCheckRequest." + }, + "PermissionCheckResponse": { + "type": "object", + "properties": { + "can": { + "$ref": "#/definitions/CheckResult", + "description": "Result of the permission check." + }, + "metadata": { + "$ref": "#/definitions/PermissionCheckResponseMetadata", + "description": "Metadata associated with this response." + } + }, + "description": "PermissionCheckResponse is the response message for the Check method in the Permission service." + }, + "PermissionCheckResponseMetadata": { + "type": "object", + "properties": { + "check_count": { + "type": "integer", + "format": "int32", + "description": "The count of the checks performed." + } + }, + "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": "#/definitions/Child", + "description": "The child related to this permission." + } + }, + "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": "#/definitions/Expand", + "description": "Expansion tree." + } + }, + "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", + "format": "int32", + "description": "Query limit when if recursive database queries got in loop." + } + }, + "description": "PermissionLookupEntityRequestMetadata metadata for the PermissionLookupEntityRequest." + }, + "PermissionLookupEntityResponse": { + "type": "object", + "properties": { + "entity_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of identifiers for entities that match the lookup." + } + }, + "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", + "format": "int32", + "description": "Query limit when if recursive database queries got in loop." + } + }, + "description": "PermissionLookupSubjectRequestMetadata metadata for the PermissionLookupSubjectRequest." + }, + "PermissionLookupSubjectResponse": { + "type": "object", + "properties": { + "subject_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of identifiers for subjects that match the lookup." + } + }, + "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", + "format": "int32", + "description": "Query limit when if recursive database queries got in loop." + } + }, + "description": "PermissionSubjectPermissionRequestMetadata metadata for the PermissionSubjectPermissionRequest." + }, + "PermissionSubjectPermissionResponse": { + "type": "object", + "properties": { + "results": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/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", + "enum": [ + "PRIMITIVE_TYPE_UNSPECIFIED", + "BOOL", + "INT64", + "UINT64", + "DOUBLE", + "STRING", + "BYTES" + ], + "default": "PRIMITIVE_TYPE_UNSPECIFIED", + "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." + }, + "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", + "items": { + "type": "object", + "$ref": "#/definitions/RelationReference" + }, + "description": "A list of references to other relations." + } + }, + "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": { + "type": "object", + "properties": { + "snap_token": { + "type": "string", + "description": "The snap token to avoid stale cache, see more details on [Snap Tokens](../../operations/snap-tokens)" + } + }, + "title": "RelationshipDeleteResponse" + }, + "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", + "items": { + "type": "object", + "$ref": "#/definitions/Tuple" + }, + "description": "tuples is a list of the relationships retrieved in the read operation, represented as entity-relation-entity triples." + }, + "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": { + "type": "object", + "properties": { + "schema_version": { + "type": "string" + } + }, + "title": "RelationshipWriteRequestMetadata" + }, + "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)" + } + }, + "title": "RelationshipWriteResponse" + }, + "Rewrite": { + "type": "object", + "properties": { + "rewriteOperation": { + "$ref": "#/definitions/Rewrite.Operation", + "description": "The type of rewrite operation to be performed." + }, + "children": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/Child" + }, + "description": "A list of children that are operated upon by the rewrite operation." + } + }, + "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", + "enum": [ + "OPERATION_UNSPECIFIED", + "OPERATION_UNION", + "OPERATION_INTERSECTION", + "OPERATION_EXCLUSION" + ], + "default": "OPERATION_UNSPECIFIED", + "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." + }, + "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": "#/definitions/AttributeType" + }, + "description": "Map of arguments for this rule. The key is the attribute name, and the value is the AttributeType." + }, + "expression": { + "$ref": "#/definitions/CheckedExpr", + "description": "The expression for this rule in the form of a google.api.expr.v1alpha1.CheckedExpr." + } + }, + "description": "The RuleDefinition message provides detailed information about a specific rule." + }, + "SchemaDefinition": { + "type": "object", + "properties": { + "entityDefinitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/EntityDefinition" + }, + "description": "Map of entity definitions. The key is the entity name, and the value is the corresponding EntityDefinition." + }, + "ruleDefinitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/RuleDefinition" + }, + "description": "Map of rule definitions. The key is the rule name, and the value is the corresponding RuleDefinition." + }, + "references": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/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", + "enum": [ + "REFERENCE_UNSPECIFIED", + "REFERENCE_ENTITY", + "REFERENCE_RULE" + ], + "default": "REFERENCE_UNSPECIFIED", + "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." + }, + "SchemaList": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "created_at": { + "type": "string" + } + }, + "title": "SchemaList provides a list of schema versions with their corresponding creation timestamps" + }, + "SchemaListResponse": { + "type": "object", + "properties": { + "head": { + "type": "string", + "title": "head of the schemas is the latest version available for the tenant" + }, + "schemas": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/SchemaList" + }, + "title": "list of schema versions with creation timestamps" + }, + "continuous_token": { + "type": "string", + "description": "continuous_token is a string that can be used to paginate and retrieve the next set of results." + } + }, + "title": "SchemaListResponse is the response message for the List method in the Schema service.\nIt returns a paginated list of schemas" + }, + "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": "#/definitions/SchemaDefinition", + "description": "schema is the SchemaDefinition that represents the read schema." + } + }, + "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": "#/definitions/Expr", + "description": "Required. The target of the selection expression.\n\nFor example, in the select expression `request.auth`, the `request`\nportion of the expression is the `operand`." + }, + "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", + "items": { + "type": "integer", + "format": "int32" + }, + "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] \u003c id_positions[id] \u003c line_offsets[i+1]`. The\ncolumn may be derivd from `id_positions[id] - line_offsets[i]`." + }, + "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": "#/definitions/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 \u003e 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": { + "type": "object", + "$ref": "#/definitions/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": { + "type": "string", + "title": "Type of the subject" + }, + "ids": { + "type": "array", + "items": { + "type": "string" + }, + "title": "List of subject IDs" + }, + "relation": { + "type": "string" + } + }, + "description": "SubjectFilter is used to filter subjects based on the type, ids and relation." + }, + "Subjects": { + "type": "object", + "properties": { + "subjects": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/Subject" + }, + "description": "A list of subjects." + } + }, + "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", + "format": "date-time", + "description": "The time at which the tenant was created." + } + }, + "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": "#/definitions/Tenant", + "description": "tenant is the created tenant information." + } + }, + "description": "TenantCreateResponse is the message returned from the request to create a tenant." + }, + "TenantDeleteResponse": { + "type": "object", + "properties": { + "tenant": { + "$ref": "#/definitions/Tenant", + "description": "tenant is the tenant information that was deleted." + } + }, + "description": "TenantDeleteResponse is the message returned from the request to delete a tenant." + }, + "TenantListRequest": { + "type": "object", + "properties": { + "page_size": { + "type": "integer", + "format": "int64", + "description": "page_size is the number of tenants to be returned in the response.\nThe value should be between 1 and 100." + }, + "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", + "items": { + "type": "object", + "$ref": "#/definitions/Tenant" + }, + "description": "tenants is a list of tenants." + }, + "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": "#/definitions/Entity" + }, + "relation": { + "type": "string" + }, + "subject": { + "$ref": "#/definitions/Subject" + } + }, + "description": "Tuple is a structure that includes an entity, a relation, and a subject." + }, + "TupleFilter": { + "type": "object", + "properties": { + "entity": { + "$ref": "#/definitions/EntityFilter" + }, + "relation": { + "type": "string" + }, + "subject": { + "$ref": "#/definitions/SubjectFilter", + "title": "The subject filter" + } + }, + "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": "#/definitions/TupleSet", + "title": "The tuple set" + }, + "computed": { + "$ref": "#/definitions/ComputedUserSet", + "title": "The computed user set" + } + }, + "description": "TupleToUserSet defines a mapping from tuple sets to computed user sets." + }, + "Values": { + "type": "object", + "properties": { + "values": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Any" + } + } + } + }, + "WatchResponse": { + "type": "object", + "properties": { + "changes": { + "$ref": "#/definitions/DataChanges", + "description": "Changes in the data." + } + }, + "description": "WatchResponse is the response message for the Watch RPC. It contains the\nchanges in the data that are being watched." + }, + "WellKnownType": { + "type": "string", + "enum": [ + "WELL_KNOWN_TYPE_UNSPECIFIED", + "ANY", + "TIMESTAMP", + "DURATION" + ], + "default": "WELL_KNOWN_TYPE_UNSPECIFIED", + "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`." + }, + "v1.Call": { + "type": "object", + "properties": { + "ruleName": { + "type": "string", + "title": "Name of the rule" + }, + "arguments": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/Argument" + }, + "title": "Arguments passed to the rule" + } + }, + "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", + "items": { + "type": "string" + }, + "description": "'relationships_write' is a repeated string field for storing relationship keys\nthat are to be written or created." + }, + "relationships_delete": { + "type": "array", + "items": { + "type": "string" + }, + "description": "'relationships_delete' is a repeated string field for storing relationship keys\nthat are to be deleted or removed." + }, + "attributes_write": { + "type": "array", + "items": { + "type": "string" + }, + "description": "'attributes_write' is a repeated string field for storing attribute keys\nthat are to be written or created." + }, + "attributes_delete": { + "type": "array", + "items": { + "type": "string" + }, + "description": "'attributes_delete' is a repeated string field for storing attribute keys\nthat are to be deleted or removed." + } + }, + "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", + "items": { + "type": "string" + }, + "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]." + }, + "value": { + "$ref": "#/definitions/Constant", + "description": "For references to constants, this may contain the value of the\nconstant if known at compile time." + } + }, + "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": "#/definitions/PrimitiveType", + "description": "Primitive types: `true`, `1u`, `-2.0`, `'string'`, `b'bytes'`." + }, + "wrapper": { + "$ref": "#/definitions/PrimitiveType", + "description": "Wrapper of a primitive type, e.g. `google.protobuf.Int64Value`." + }, + "wellKnown": { + "$ref": "#/definitions/WellKnownType", + "description": "Well-known protobuf type such as `google.protobuf.Timestamp`." + }, + "listType": { + "$ref": "#/definitions/ListType", + "description": "Parameterized list with elements of `list_type`, e.g. `list\u003ctimestamp\u003e`." + }, + "mapType": { + "$ref": "#/definitions/MapType", + "description": "Parameterized map with typed keys and values." + }, + "function": { + "$ref": "#/definitions/FunctionType", + "description": "Function type." + }, + "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\u003cE\u003e`\nwould be a `list_type` whose element type was a `type_param` type\nnamed `E`." + }, + "type": { + "$ref": "#/definitions/v1alpha1.Type", + "description": "Type type.\n\nThe `type` value specifies the target type. e.g. int is type with a\ntarget type of `Primitive.INT`." + }, + "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": "#/definitions/AbstractType", + "description": "Abstract, application defined type." + } + }, + "description": "Represents a CEL type." + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} diff --git a/docs/api-reference/openapi2.json b/docs/api-reference/openapi2.json deleted file mode 100644 index b1509be04..000000000 --- a/docs/api-reference/openapi2.json +++ /dev/null @@ -1,195 +0,0 @@ -{ - "openapi": "3.0.1", - "info": { - "title": "OpenAPI Plant Store", - "description": "A sample API that uses a plant store as an example to demonstrate features in the OpenAPI specification", - "license": { - "name": "MIT" - }, - "version": "1.0.0" - }, - "servers": [ - { - "url": "http://sandbox.mintlify.com" - } - ], - "security": [ - { - "bearerAuth": [] - } - ], - "paths": { - "/plants": { - "get": { - "description": "Returns all plants from the system that the user has access to", - "parameters": [ - { - "name": "limit", - "in": "query", - "description": "The maximum number of results to return", - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Plant response", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Plant" - } - } - } - } - }, - "400": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - }, - "post": { - "description": "Creates a new plant in the store", - "requestBody": { - "description": "Plant to add to the store", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NewPlant" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "plant response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Plant" - } - } - } - }, - "400": { - "description": "unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/plants/{id}": { - "delete": { - "description": "Deletes a single plant based on the ID supplied", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "ID of plant to delete", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "204": { - "description": "Plant deleted", - "content": {} - }, - "400": { - "description": "unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "Plant": { - "required": [ - "name" - ], - "type": "object", - "properties": { - "name": { - "description": "The name of the plant", - "type": "string" - }, - "tag": { - "description": "Tag to specify the type", - "type": "string" - } - } - }, - "NewPlant": { - "allOf": [ - { - "$ref": "#/components/schemas/Plant" - }, - { - "required": [ - "id" - ], - "type": "object", - "properties": { - "id": { - "description": "Identification number of the plant", - "type": "integer", - "format": "int64" - } - } - } - ] - }, - "Error": { - "required": [ - "error", - "message" - ], - "type": "object", - "properties": { - "error": { - "type": "integer", - "format": "int32" - }, - "message": { - "type": "string" - } - } - } - }, - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer" - } - } - } -} \ No newline at end of file From 1eb7a4ae859c5a3ddae6cf9959c138dcdf0cbb0e Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Tue, 19 Mar 2024 21:26:29 +0300 Subject: [PATCH 55/70] docs: field descriptions added --- pkg/pb/base/v1/service.pb.go | 4955 +++++++++++++------------ pkg/pb/base/v1/service.pb.validate.go | 16 +- proto/base/v1/service.proto | 340 +- 3 files changed, 2897 insertions(+), 2414 deletions(-) diff --git a/pkg/pb/base/v1/service.pb.go b/pkg/pb/base/v1/service.pb.go index 328cb8297..59b8b8e4d 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,1319 +4199,1673 @@ 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, 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, + 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, 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, + 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, - 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, 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, 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, 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, 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, + 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, 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, + 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, 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, + 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, 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, + 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, 0xa8, 0x03, 0x0a, 0x06, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x12, 0x9d, 0x03, 0x1a, 0x9a, 0x03, 0x63, 0x75, 0x72, 0x6c, 0x20, 0x2d, + 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, 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, 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, + 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, 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, 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, + 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, 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, 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, + 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, 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, 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, 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, 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, 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, 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, + 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, 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, + 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, 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, + 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x57, 0x72, 0x69, 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, 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, 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, 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, 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, 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, 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, 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, + 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, 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, - 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, + 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, 0xa1, 0x06, 0x2a, 0x9e, 0x06, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, + 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, 0xfa, 0x05, 0x0a, - 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0xef, 0x05, 0x1a, 0xec, 0x05, 0x63, 0x75, 0x72, + 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, 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, 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, + 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, @@ -5282,368 +5874,235 @@ var file_base_v1_service_proto_rawDesc = []byte{ 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, + 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, 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, + 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, - 0xbb, 0x03, 0x2a, 0xb8, 0x03, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, + 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, 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, + 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, 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, 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, + 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, 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, 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, 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, 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, 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, @@ -5655,360 +6114,312 @@ var file_base_v1_service_proto_rawDesc = []byte{ 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, 0x5d, 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, + 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, - 0xec, 0x01, 0x2a, 0xe9, 0x01, 0x0a, 0x0f, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x06, + 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, 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, + 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, 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, 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, + 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, 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, 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, + 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, 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, 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, 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, + 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 f1b5867f9..fe7b6f590 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/service.proto b/proto/base/v1/service.proto index 5042965ca..1e606b318 100644 --- a/proto/base/v1/service.proto +++ b/proto/base/v1/service.proto @@ -449,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. @@ -501,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. @@ -514,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]; @@ -540,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. @@ -563,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]; @@ -593,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. @@ -626,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]; @@ -645,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 @@ -664,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]; @@ -690,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. @@ -716,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]; @@ -735,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. @@ -827,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 @@ -1084,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"]; @@ -1110,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]; @@ -1144,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. @@ -1583,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]; @@ -1626,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]; @@ -1661,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]; @@ -1695,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. @@ -1712,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]; @@ -1739,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. @@ -1756,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]; @@ -1773,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"]; @@ -1811,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 ** @@ -2037,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. } @@ -2053,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"]; } @@ -2069,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. } From be8e778d738ef7432cf2a9537ad0517bdff3465c Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Tue, 19 Mar 2024 21:27:41 +0300 Subject: [PATCH 56/70] chore: remove serve-docs from Makefile due to documentation updates --- Makefile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 9449ed1fc..697427331 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 From f9afab0076452844e34e73ed9f7a831b4f4f9101 Mon Sep 17 00:00:00 2001 From: EgeAytin Date: Tue, 19 Mar 2024 21:49:32 +0300 Subject: [PATCH 57/70] docs: fied the urls according to the new doc --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8a1798653..5bbae0642 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 From ba3b174e2f7ea6ed2aa5762a689337a0188d3f5c Mon Sep 17 00:00:00 2001 From: EgeAytin Date: Wed, 20 Mar 2024 10:45:04 +0300 Subject: [PATCH 58/70] docs: add plausible analytics snippet --- docs/mint.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/mint.json b/docs/mint.json index 526a0692c..ccde0857d 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -22,6 +22,11 @@ "url": "https://discord.com/invite/n6KfzYxhPp" } ], + "analytics": { + "plausible": { + "domain": "docs.permify.co" + } + }, "topbarCtaButton": { "name": "Playground", "url": "https://play.permify.co/?s=organizations-hierarchies" From 63cb05ca870b88ce7f1e68d3e020a632d28d4c8e Mon Sep 17 00:00:00 2001 From: EgeAytin Date: Wed, 20 Mar 2024 17:28:17 +0300 Subject: [PATCH 59/70] docs: add dark as the default mode --- docs/mint.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/mint.json b/docs/mint.json index ccde0857d..4c5bbe798 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -22,6 +22,9 @@ "url": "https://discord.com/invite/n6KfzYxhPp" } ], + "modeToggle": { + "default": "dark" + }, "analytics": { "plausible": { "domain": "docs.permify.co" From 9eca68763d38456a05184ca540d616131fc1ae8f Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Wed, 20 Mar 2024 19:28:59 +0300 Subject: [PATCH 60/70] feat(oidc): update existing implementation with lestrrat-go/jwx/jwk library --- go.mod | 22 +++- go.sum | 35 +++++- internal/authn/oidc/authn.go | 208 ++++++++++++++--------------------- internal/config/config.go | 14 ++- pkg/cmd/flags/serve.go | 18 ++- 5 files changed, 154 insertions(+), 143 deletions(-) diff --git a/go.mod b/go.mod index ba4a3f36b..95e0462d7 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/jackc/pgtype v1.14.0 github.com/jackc/pgx/v5 v5.5.1 github.com/juju/ratelimit v1.0.2 + github.com/lestrrat-go/jwx v1.2.29 github.com/onsi/ginkgo/v2 v2.13.2 github.com/onsi/gomega v1.30.0 github.com/pkg/errors v0.9.1 @@ -37,7 +38,7 @@ 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/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.27.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 go.opentelemetry.io/contrib/instrumentation/host v0.46.1 @@ -54,7 +55,7 @@ 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/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 @@ -63,7 +64,16 @@ require ( 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/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 +) require ( dario.cat/mergo v1.0.0 // indirect @@ -134,7 +144,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 +153,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.19.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/mod v0.13.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.14.0 // indirect google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 // indirect diff --git a/go.sum b/go.sum index 70b8f9679..36065a9d9 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,9 @@ 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= @@ -141,6 +144,8 @@ 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= @@ -329,6 +334,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= @@ -454,8 +472,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,8 +484,9 @@ 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= @@ -555,8 +575,9 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y 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.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= 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= @@ -632,8 +653,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx 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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +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= @@ -716,14 +737,16 @@ 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.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= 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= diff --git a/internal/authn/oidc/authn.go b/internal/authn/oidc/authn.go index fbc993f5d..76ce03f31 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,163 +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 - - // Last time the JWKS was fetched - lastKeyFetch time.Time - - // Last time the OIDC configuration was fetched - lastOIDCConfigFetch time.Time - - // KeyRefreshInterval is the interval to refresh the keys - keyRefreshInterval time.Duration - - // ConfigRefreshInterval is the interval to refresh the OIDC configuration - configRefreshInterval time.Duration - - // refreshUnknownKID is a flag to refresh the JWKS when the KID is unknown - refreshUnknownKID bool + // 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, conf 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: conf.Issuer, - Audience: conf.Audience, - httpClient: client.StandardClient(), // Wrap retryable client as a standard http.Client - keyRefreshInterval: conf.KeyRefreshInterval, - configRefreshInterval: conf.ConfigRefreshInterval, - refreshUnknownKID: conf.RefreshUnknownKID, + 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. } - 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 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) + } - // Parse and validate the JWT from the authentication header. - token, err := jwtParser.Parse(authHeader, func(token *jwt.Token) (any, error) { - // If a presented token's KID is not found in the existing headers, initiate a JWKs fetch and validate the token. - if _, ok := token.Header["kid"].(string); !ok { - // Whem KID is absent in the header and it has been less than the interval since the last JWKs retrieval attempt, reject the token. - if !oidc.refreshUnknownKID && time.Since(oidc.lastKeyFetch) < oidc.keyRefreshInterval { - return nil, errors.New(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()) + // 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) } - - // Use the JWKS from oidc to validate the JWT's signature. - return oidc.JWKs.Keyfunc(token) + // 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 { - if oidc.JwksURI == "" || time.Since(oidc.lastOIDCConfigFetch) > oidc.configRefreshInterval { - oidcConfig, err := oidc.fetchOIDCConfiguration() - if err != nil { - return fmt.Errorf("error fetching OIDC configuration: %w", err) - } - - oidc.JwksURI = oidcConfig.JWKsURI - oidc.lastOIDCConfigFetch = time.Now() - } - - jwks, err := oidc.GetKeys() - if err != nil { - return fmt.Errorf("error fetching OIDC keys: %w", err) - } - - oidc.JWKs = jwks - oidc.lastKeyFetch = time.Now() - - 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: oidc.keyRefreshInterval, // Set the interval to refresh the keys. - RefreshUnknownKID: oidc.refreshUnknownKID, // Set the flag to refresh the JWKS when the KID is unknown. - }) - 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. @@ -189,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 { @@ -219,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) } @@ -261,7 +225,3 @@ func parseOIDCConfiguration(body []byte) (*Config, error) { // 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 2ce0f4bb1..8d4411e4e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -69,11 +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 - ConfigRefreshInterval time.Duration `mapstructure:"config_refresh_interval"` // Interval to refresh the OIDC configuration - KeyRefreshInterval time.Duration `mapstructure:"key_refresh_interval"` // Interval to refresh the keys - RefreshUnknownKID bool `mapstructure:"refresh_unknown_kid"` // Flag to enable/disable auto fetching of keys when KID is not found in the header + 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. @@ -303,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/pkg/cmd/flags/serve.go b/pkg/cmd/flags/serve.go index 3dfd506d5..2e255566c 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 { From 180fc1180d998255510022fa847a1810e77db8d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:24:49 +0000 Subject: [PATCH 61/70] build(deps): bump github.com/docker/docker Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.7+incompatible to 24.0.9+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v24.0.7...v24.0.9) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 3 +-- go.sum | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 95e0462d7..a3833477d 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ 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 @@ -87,7 +86,7 @@ require ( 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/docker v24.0.9+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect diff --git a/go.sum b/go.sum index 36065a9d9..fbf6fcecd 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= @@ -104,8 +102,8 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczC 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/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= +github.com/docker/docker v24.0.9+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/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -149,7 +147,6 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG 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= From e3c87904d194b247b4582f346c462b32b8a5b98f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 05:59:37 +0000 Subject: [PATCH 62/70] build(deps): bump actions/dependency-review-action from 4.0.0 to 4.2.3 Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.0.0 to 4.2.3. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/4901385134134e04cec5fbe5ddfe3b2c5bd5d976...0fa40c3c10055986a88de3baa0d6ec17c5a894b3) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index fc3a42dfa..8ae225315 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 From 3bcc60cdd761842168393ceb344b3e8a83780cfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 17:39:04 +0000 Subject: [PATCH 63/70] build(deps): bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/sdk-generator.yml | 2 +- .github/workflows/sync.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sdk-generator.yml b/.github/workflows/sdk-generator.yml index 8425fc1d1..0aae250d6 100644 --- a/.github/workflows/sdk-generator.yml +++ b/.github/workflows/sdk-generator.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Generate Python Client uses: openapi-generators/openapitools-generator-action@v1 diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 64035d351..9d5b3b4d8 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -14,7 +14,7 @@ jobs: egress-policy: audit - name: Checkout Repository - uses: actions/checkout@61b9e3751b92087fd0b06925ba6dd6314e06f089 # master + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # master - name: Run GitHub File Sync uses: BetaHuhn/repo-file-sync-action@3023dac7ce66c18b119e2012348437eadeaea116 # v1.21.0 with: From 8664186710db30ab2aaa0b11320926f175a6dace Mon Sep 17 00:00:00 2001 From: StepSecurity Bot Date: Thu, 21 Mar 2024 17:48:03 +0000 Subject: [PATCH 64/70] [StepSecurity] ci: Harden GitHub Actions Signed-off-by: StepSecurity Bot --- .github/workflows/sdk-generator.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sdk-generator.yml b/.github/workflows/sdk-generator.yml index 0aae250d6..06de37d2e 100644 --- a/.github/workflows/sdk-generator.yml +++ b/.github/workflows/sdk-generator.yml @@ -22,11 +22,16 @@ jobs: 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@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Generate Python Client - uses: openapi-generators/openapitools-generator-action@v1 + uses: openapi-generators/openapitools-generator-action@d27bd4385276f24d23ea92157dfdf4c47be4bbca # v1 with: generator: ${{ matrix.language }} openapi-file: ${SWAGGER_PATH} From d273d00a771430b94a622a9e1405ea72b26c54e9 Mon Sep 17 00:00:00 2001 From: Tolga Ozen <39353278+tolgaOzen@users.noreply.github.com> Date: Thu, 21 Mar 2024 20:49:50 +0300 Subject: [PATCH 65/70] fix: swagger path --- .github/workflows/sdk-generator.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sdk-generator.yml b/.github/workflows/sdk-generator.yml index 0aae250d6..bc61a36b6 100644 --- a/.github/workflows/sdk-generator.yml +++ b/.github/workflows/sdk-generator.yml @@ -15,7 +15,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.SDK_GH_TOKEN }} ORG_NAME: permify - SWAGGER_PATH: docs/apidocs.swagger.json + SWAGGER_PATH: docs/api-reference/apidocs.swagger.json strategy: matrix: From fe09f94b17c12647d1435cc27f54bd71b6ac860f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Mar 2024 11:01:19 +0000 Subject: [PATCH 66/70] build(deps): bump github.com/jackc/pgx/v5 from 5.5.1 to 5.5.4 Bumps [github.com/jackc/pgx/v5](https://github.com/jackc/pgx) from 5.5.1 to 5.5.4. - [Changelog](https://github.com/jackc/pgx/blob/master/CHANGELOG.md) - [Commits](https://github.com/jackc/pgx/compare/v5.5.1...v5.5.4) --- updated-dependencies: - dependency-name: github.com/jackc/pgx/v5 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 983cb2d8d..27483b3b2 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.5 github.com/jackc/pgio v1.0.0 github.com/jackc/pgtype v1.14.2 - github.com/jackc/pgx/v5 v5.5.1 + 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.13.2 diff --git a/go.sum b/go.sum index f83b70c97..865d29746 100644 --- a/go.sum +++ b/go.sum @@ -299,8 +299,8 @@ github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9 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= From 63f91ee962241d99da61e6148dcf132941b01493 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Mar 2024 11:01:45 +0000 Subject: [PATCH 67/70] build(deps): bump github.com/testcontainers/testcontainers-go Bumps [github.com/testcontainers/testcontainers-go](https://github.com/testcontainers/testcontainers-go) from 0.27.0 to 0.29.1. - [Release notes](https://github.com/testcontainers/testcontainers-go/releases) - [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.27.0...v0.29.1) --- updated-dependencies: - dependency-name: github.com/testcontainers/testcontainers-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 22 ++++++++++++---------- go.sum | 44 ++++++++++++++++++++++++-------------------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 983cb2d8d..6f5f52f10 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.17.0 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.27.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 @@ -65,6 +65,8 @@ require ( 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 @@ -72,6 +74,8 @@ require ( 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 ( @@ -81,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.9+incompatible // indirect - github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/docker v25.0.3+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 @@ -98,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 @@ -123,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.12 // 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 @@ -135,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 @@ -153,7 +155,7 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.21.0 // indirect - golang.org/x/mod v0.13.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.14.0 // indirect diff --git a/go.sum b/go.sum index f83b70c97..958778a10 100644 --- a/go.sum +++ b/go.sum @@ -77,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= @@ -100,12 +100,12 @@ github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWa 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.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= -github.com/docker/docker v24.0.9+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.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ= +github.com/docker/docker v25.0.3+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= @@ -121,6 +121,8 @@ github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU 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= @@ -214,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= @@ -374,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= @@ -386,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.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= -github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= +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= @@ -437,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= @@ -486,8 +488,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT 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= @@ -512,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= @@ -612,8 +616,8 @@ 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.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.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +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= From b12ef67ac1f0c3d2568fb4c3e0eed73364d921b6 Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Fri, 22 Mar 2024 14:04:03 +0300 Subject: [PATCH 68/70] test: restart PostgreSQL container after config change in tests --- internal/storage/postgres/postgres_test.go | 34 ++++++++++------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/internal/storage/postgres/postgres_test.go b/internal/storage/postgres/postgres_test.go index 2fec9386b..c56dc779e 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") From bc8179a8ecde74ee5ce2bc408592b21163b4729d Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Fri, 22 Mar 2024 14:04:59 +0300 Subject: [PATCH 69/70] build: version info update --- docs/api-reference/apidocs.swagger.json | 2 +- internal/info.go | 2 +- pkg/pb/base/v1/openapi.pb.go | 2 +- proto/base/v1/openapi.proto | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api-reference/apidocs.swagger.json b/docs/api-reference/apidocs.swagger.json index d90386e76..bdf047222 100644 --- a/docs/api-reference/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.7", + "version": "v0.7.8", "contact": { "name": "API Support", "url": "https://github.com/Permify/permify/issues", diff --git a/internal/info.go b/internal/info.go index 67f680113..cc29803d1 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.7" + Version = "v0.7.8" ) // Function to create a single line of the ASCII art with centered content and color diff --git a/pkg/pb/base/v1/openapi.pb.go b/pkg/pb/base/v1/openapi.pb.go index 936a5be82..a1fa8e87d 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, 0x37, 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/proto/base/v1/openapi.proto b/proto/base/v1/openapi.proto index 2328db138..c08a73e3f 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.7"; + version: "v0.7.8"; contact: { name: "API Support"; url: "https://github.com/Permify/permify/issues"; From c8b64ff76a7e56de881a16180150ec71f139019e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Mar 2024 11:20:35 +0000 Subject: [PATCH 70/70] build(deps): bump github.com/docker/docker Bumps [github.com/docker/docker](https://github.com/docker/docker) from 25.0.3+incompatible to 25.0.5+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v25.0.3...v25.0.5) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cebd68cbc..cf614a966 100644 --- a/go.mod +++ b/go.mod @@ -89,7 +89,7 @@ require ( 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/docker v25.0.3+incompatible // 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 diff --git a/go.sum b/go.sum index 0cf571194..9146ebb21 100644 --- a/go.sum +++ b/go.sum @@ -102,8 +102,8 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczC github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= 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.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ= -github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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=