diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index c88db48a08..aaa5d90bcd 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -39,7 +39,7 @@ jobs: - uses: "actions/checkout@v3" - uses: "actions/setup-go@v3" with: - go-version: "~1.18" + go-version: "~1.19" - uses: "authzed/actions/go-build@main" image-build: @@ -49,7 +49,7 @@ jobs: - uses: "actions/checkout@v3" - uses: "actions/setup-go@v3" with: - go-version: "~1.18" + go-version: "~1.19" - uses: "authzed/actions/docker-build@main" with: push: false @@ -68,7 +68,7 @@ jobs: - uses: "actions/checkout@v3" - uses: "actions/setup-go@v3" with: - go-version: "~1.18" + go-version: "~1.19" - uses: "authzed/actions/go-test@main" with: tags: "ci" @@ -80,7 +80,7 @@ jobs: - uses: "actions/checkout@v3" - uses: "actions/setup-go@v3" with: - go-version: "~1.18" + go-version: "~1.19" - name: "Cache Binaries" id: "cache-binaries" uses: "actions/cache@v2" @@ -128,7 +128,7 @@ jobs: - uses: "actions/checkout@v3" - uses: "actions/setup-go@v3" with: - go-version: "~1.18" + go-version: "~1.19" - name: "Install Go Tools" run: "./hack/install-tools.sh" - uses: "authzed/actions/buf-generate@main" diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 3c62467535..3c2b0c1edf 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,6 +1,6 @@ --- name: "Lint" -on: # yamllint disable-line rule:truthy +on: # yamllint disable-line rule:truthy push: branches: - "!dependabot/*" @@ -15,7 +15,7 @@ jobs: - uses: "actions/checkout@v3" - uses: "actions/setup-go@v3" with: - go-version: "~1.18" + go-version: "~1.19" - uses: "authzed/actions/go-test@main" with: working_directory: "tools/analyzers" @@ -27,7 +27,7 @@ jobs: - uses: "actions/checkout@v3" - uses: "actions/setup-go@v3" with: - go-version: "~1.18" + go-version: "~1.19" - uses: "authzed/actions/gofumpt@main" - uses: "authzed/actions/gofumpt@main" with: @@ -48,7 +48,7 @@ jobs: with: working_directory: "tools/analyzers" - name: "Run custom analyzers" - run: "./tools/analyzers/analyzers -skip-pkg \"github.com/authzed/spicedb/pkg/proto/dispatch/v1\" -disallowed-nil-return-type-paths \"*github.com/authzed/spicedb/pkg/proto/dispatch/v1.DispatchCheckResponse,*github.com/authzed/spicedb/pkg/proto/dispatch/v1.DispatchExpandResponse,*github.com/authzed/spicedb/pkg/proto/dispatch/v1.DispatchLookupResponse\" ./..." + run: './tools/analyzers/analyzers -skip-pkg "github.com/authzed/spicedb/pkg/proto/dispatch/v1" -disallowed-nil-return-type-paths "*github.com/authzed/spicedb/pkg/proto/dispatch/v1.DispatchCheckResponse,*github.com/authzed/spicedb/pkg/proto/dispatch/v1.DispatchExpandResponse,*github.com/authzed/spicedb/pkg/proto/dispatch/v1.DispatchLookupResponse" ./...' extra-lint: name: "Lint YAML & Markdown" @@ -92,4 +92,4 @@ jobs: - name: "Upload Trivy scan results to GitHub Security tab" uses: "github/codeql-action/upload-sarif@v2" with: - sarif_file: 'trivy-results.sarif' + sarif_file: "trivy-results.sarif" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 07a7094787..47cf390909 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,6 +1,6 @@ --- name: "Release" -on: # yamllint disable-line rule:truthy +on: # yamllint disable-line rule:truthy push: tags: - "*" @@ -16,7 +16,7 @@ jobs: fetch-depth: 0 - uses: "actions/setup-go@v3" with: - go-version: "~1.18" + go-version: "~1.19" - uses: "authzed/actions/docker-login@main" with: quayio_token: "${{ secrets.QUAYIO_PASSWORD }}" diff --git a/Dockerfile b/Dockerfile index 563137b8c1..b5bde0ae52 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.18-alpine3.15 AS spicedb-builder +FROM golang:1.19-alpine3.16 AS spicedb-builder WORKDIR /go/src/app RUN apk update && apk add --no-cache git COPY . . diff --git a/e2e/go.mod b/e2e/go.mod index 0dabf13901..08bb14b86c 100644 --- a/e2e/go.mod +++ b/e2e/go.mod @@ -3,7 +3,7 @@ module github.com/authzed/spicedb/e2e go 1.18 require ( - github.com/authzed/authzed-go v0.6.1-0.20220721164311-7b705b328aed + github.com/authzed/authzed-go v0.6.1-0.20220829195957-23aec9014d2f github.com/authzed/grpcutil v0.0.0-20220104222419-f813f77722e5 github.com/authzed/spicedb v1.5.0 github.com/brianvoe/gofakeit/v6 v6.15.0 @@ -25,7 +25,7 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.12.1 // indirect github.com/jackc/pgio v1.0.0 // indirect @@ -36,11 +36,11 @@ require ( github.com/shopspring/decimal v1.3.1 // indirect golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect golang.org/x/text v0.3.7 // indirect - google.golang.org/genproto v0.0.0-20220728213248-dd149ef739b9 // indirect + google.golang.org/genproto v0.0.0-20220819174105-e9f053255caa // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/e2e/go.sum b/e2e/go.sum index cf079395eb..d8b688238e 100644 --- a/e2e/go.sum +++ b/e2e/go.sum @@ -6,8 +6,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/authzed/authzed-go v0.6.1-0.20220721164311-7b705b328aed h1:FX1+vz+Z/eOMeGUmz4XoDinFhrx4R74tKG8z29Var8o= -github.com/authzed/authzed-go v0.6.1-0.20220721164311-7b705b328aed/go.mod h1:q211JgPx8zRvU4IkwrdCh810PfDTr5eWpvbtD661AdE= +github.com/authzed/authzed-go v0.6.1-0.20220829195957-23aec9014d2f h1:hmtxtXGcUCxGuA3bJGTy0qK+18tUlVvVFMvyauebW34= +github.com/authzed/authzed-go v0.6.1-0.20220829195957-23aec9014d2f/go.mod h1:m4JRMHkPM/pVqcvrON8wkYGIBILqkbMno1UNL62rXN0= github.com/authzed/grpcutil v0.0.0-20220104222419-f813f77722e5 h1:sZM7XzdyuLyxj7pC/g7uX+XAqZ7m6NMxZzuQRovgBPw= github.com/authzed/grpcutil v0.0.0-20220104222419-f813f77722e5/go.mod h1:rqjY3zyK/YP7NID9+B2BdIRRkvnK+cdf9/qya/zaFZE= github.com/brianvoe/gofakeit/v6 v6.15.0 h1:lJPGJZ2/07TRGDazyTzD5b18N3y4tmmJpdhCUw18FlI= @@ -97,8 +97,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.1 h1:/sDbPb60SusIXjiJGYLUoS/rAQurQmvGWmwn2bBPM9c= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.1/go.mod h1:G+WkljZi4mflcqVxYSgvt8MNctRQHjEH8ubKtt1Ka3w= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2 h1:BqHID5W5qnMkug0Z8UmL8tN0gAy4jQ+B4WFt8cCgluU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2/go.mod h1:ZbS3MZTZq/apAfAEHGoB5HbsQQstoqP92SjAqtQ9zeg= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= @@ -268,8 +268,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E= +golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c h1:q3gFqPqH7NVofKo3c3yETAP//pPI+G5mvB7qqj1Y5kY= @@ -304,8 +304,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U= +golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -349,8 +349,8 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20220728213248-dd149ef739b9 h1:d3fKQZK+1rWQMg3xLKQbPMirUCo29I/NRdI2WarSzTg= -google.golang.org/genproto v0.0.0-20220728213248-dd149ef739b9/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220819174105-e9f053255caa h1:Ux9yJCyf598uEniFPSyp8g1jtGTt77m+lzYyVgrWQaQ= +google.golang.org/genproto v0.0.0-20220819174105-e9f053255caa/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= diff --git a/go.mod b/go.mod index e43f66411e..f1f138e43e 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/authzed/spicedb -go 1.18 +go 1.19 require ( cloud.google.com/go/spanner v1.36.0 github.com/Masterminds/squirrel v1.5.3 - github.com/authzed/authzed-go v0.6.1-0.20220721164311-7b705b328aed + github.com/authzed/authzed-go v0.6.1-0.20220829195957-23aec9014d2f github.com/authzed/grpcutil v0.0.0-20220104222419-f813f77722e5 github.com/aws/aws-sdk-go v1.44.67 github.com/benbjohnson/clock v1.3.0 @@ -30,7 +30,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware/providers/zerolog/v2 v2.0.0-rc.2.0.20210831071041-dd1540ef8252 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.2.0.20210831071041-dd1540ef8252 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.1 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2 github.com/hashicorp/go-memdb v1.3.3 github.com/influxdata/tdigest v0.0.1 github.com/jackc/pgconn v1.12.1 @@ -60,11 +60,12 @@ require ( go.opentelemetry.io/otel v1.8.0 go.opentelemetry.io/otel/trace v1.8.0 go.uber.org/goleak v1.1.12 + golang.org/x/exp v0.0.0-20220823124025-807a23277127 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 golang.org/x/tools v0.1.12 google.golang.org/api v0.90.0 - google.golang.org/genproto v0.0.0-20220728213248-dd149ef739b9 + google.golang.org/genproto v0.0.0-20220819174105-e9f053255caa google.golang.org/grpc v1.48.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 google.golang.org/protobuf v1.28.1 @@ -122,7 +123,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect - github.com/lyft/protoc-gen-star v0.6.0 // indirect + github.com/lyft/protoc-gen-star v0.6.1 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect @@ -143,7 +144,7 @@ require ( github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500 // indirect github.com/sirupsen/logrus v1.8.1 // indirect - github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/viper v1.11.0 // indirect @@ -168,9 +169,9 @@ require ( go.uber.org/multierr v1.8.0 // indirect golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect - golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect diff --git a/go.sum b/go.sum index 7d1bf1aa5e..ef7205bee1 100644 --- a/go.sum +++ b/go.sum @@ -95,8 +95,8 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/authzed/authzed-go v0.6.1-0.20220721164311-7b705b328aed h1:FX1+vz+Z/eOMeGUmz4XoDinFhrx4R74tKG8z29Var8o= -github.com/authzed/authzed-go v0.6.1-0.20220721164311-7b705b328aed/go.mod h1:q211JgPx8zRvU4IkwrdCh810PfDTr5eWpvbtD661AdE= +github.com/authzed/authzed-go v0.6.1-0.20220829195957-23aec9014d2f h1:hmtxtXGcUCxGuA3bJGTy0qK+18tUlVvVFMvyauebW34= +github.com/authzed/authzed-go v0.6.1-0.20220829195957-23aec9014d2f/go.mod h1:m4JRMHkPM/pVqcvrON8wkYGIBILqkbMno1UNL62rXN0= github.com/authzed/grpcutil v0.0.0-20220104222419-f813f77722e5 h1:sZM7XzdyuLyxj7pC/g7uX+XAqZ7m6NMxZzuQRovgBPw= github.com/authzed/grpcutil v0.0.0-20220104222419-f813f77722e5/go.mod h1:rqjY3zyK/YP7NID9+B2BdIRRkvnK+cdf9/qya/zaFZE= github.com/aws/aws-sdk-go v1.17.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -374,8 +374,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92Bcuy github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.1 h1:/sDbPb60SusIXjiJGYLUoS/rAQurQmvGWmwn2bBPM9c= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.1/go.mod h1:G+WkljZi4mflcqVxYSgvt8MNctRQHjEH8ubKtt1Ka3w= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2 h1:BqHID5W5qnMkug0Z8UmL8tN0gAy4jQ+B4WFt8cCgluU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2/go.mod h1:ZbS3MZTZq/apAfAEHGoB5HbsQQstoqP92SjAqtQ9zeg= github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -505,8 +505,9 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lyft/protoc-gen-star v0.6.0 h1:xOpFu4vwmIoUeUrRuAtdCrZZymT/6AkW/bsUWA506Fo= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1 h1:erE0rdztuaDq3bpGifD95wfoPrSZc95nGA6tbiNYh6M= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -661,8 +662,8 @@ github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= -github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= @@ -798,8 +799,9 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220823124025-807a23277127 h1:S4NrSKDfihhl3+4jSTgwoIevKxX9p7Iv9x++OEIptDo= +golang.org/x/exp v0.0.0-20220823124025-807a23277127/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -883,8 +885,8 @@ golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E= +golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 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= @@ -1011,8 +1013,8 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U= +golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= @@ -1249,8 +1251,8 @@ google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljW google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220728213248-dd149ef739b9 h1:d3fKQZK+1rWQMg3xLKQbPMirUCo29I/NRdI2WarSzTg= -google.golang.org/genproto v0.0.0-20220728213248-dd149ef739b9/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220819174105-e9f053255caa h1:Ux9yJCyf598uEniFPSyp8g1jtGTt77m+lzYyVgrWQaQ= +google.golang.org/genproto v0.0.0-20220819174105-e9f053255caa/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/internal/dispatch/caching/caching.go b/internal/dispatch/caching/caching.go index 5e78fdcbc7..7eb6f45178 100644 --- a/internal/dispatch/caching/caching.go +++ b/internal/dispatch/caching/caching.go @@ -432,8 +432,11 @@ func (cd *Dispatcher) DispatchLookupSubjects(req *v1.DispatchLookupSubjectsReque mu.Lock() defer mu.Unlock() - for _, id := range result.FoundSubjectIds { - estimatedSize += int64(len(id)) + for _, found := range result.FoundSubjects { + estimatedSize += int64(len(found.SubjectId)) + for _, excludedID := range found.ExcludedSubjectIds { + estimatedSize += int64(len(excludedID)) + } } toCacheResults = append(toCacheResults, adjustedResult) diff --git a/internal/dispatch/dispatch.go b/internal/dispatch/dispatch.go index 7bd0f27ae5..10f8c211ce 100644 --- a/internal/dispatch/dispatch.go +++ b/internal/dispatch/dispatch.go @@ -166,3 +166,18 @@ func LookupSubjectsRequestToKey(req *v1.DispatchLookupSubjectsRequest) string { req.Metadata.AtRevision, ) } + +// AddResponseMetadata adds the metadata found in the incoming metadata to the existing +// metadata, *modifying it in place*. +func AddResponseMetadata(existing *v1.ResponseMeta, incoming *v1.ResponseMeta) { + existing.DispatchCount += incoming.DispatchCount + existing.CachedDispatchCount += incoming.CachedDispatchCount + existing.DepthRequired = max(existing.DepthRequired, incoming.DepthRequired) +} + +func max(x, y uint32) uint32 { + if x < y { + return y + } + return x +} diff --git a/internal/dispatch/graph/check_test.go b/internal/dispatch/graph/check_test.go index f5f677858a..b891df9bf0 100644 --- a/internal/dispatch/graph/check_test.go +++ b/internal/dispatch/graph/check_test.go @@ -196,7 +196,7 @@ func TestCheckMetadata(t *testing.T) { []expected{ {"owner", true, 1, 1}, {"edit", true, 3, 2}, - {"view", true, 21, 3}, + {"view", true, 21, 5}, }, }, { diff --git a/internal/dispatch/graph/lookupsubjects_test.go b/internal/dispatch/graph/lookupsubjects_test.go index 66a7523eb9..58d12067fa 100644 --- a/internal/dispatch/graph/lookupsubjects_test.go +++ b/internal/dispatch/graph/lookupsubjects_test.go @@ -140,7 +140,13 @@ func TestSimpleLookupSubjects(t *testing.T) { foundSubjectIds := []string{} for _, result := range stream.Results() { - foundSubjectIds = append(foundSubjectIds, result.FoundSubjectIds...) + for _, found := range result.FoundSubjects { + if len(found.ExcludedSubjectIds) > 0 { + continue + } + + foundSubjectIds = append(foundSubjectIds, found.SubjectId) + } } sort.Strings(foundSubjectIds) @@ -211,7 +217,7 @@ func TestLookupSubjectsDispatchCount(t *testing.T) { "view", "user", "...", - 4, + 13, }, { "document", diff --git a/internal/dispatch/stream.go b/internal/dispatch/stream.go index ebb4305fb3..af02003e26 100644 --- a/internal/dispatch/stream.go +++ b/internal/dispatch/stream.go @@ -114,6 +114,38 @@ func StreamWithContext[T any](context context.Context, stream Stream[T]) Stream[ } } +// HandlingDispatchStream is a dispatch stream that executes a handler for each item published. +// It uses an internal mutex to ensure it is thread safe. +type HandlingDispatchStream[T any] struct { + ctx context.Context + processor func(result T) error + mu sync.Mutex +} + +// NewHandlingDispatchStream returns a new handling dispatch stream. +func NewHandlingDispatchStream[T any](ctx context.Context, processor func(result T) error) Stream[T] { + return &HandlingDispatchStream[T]{ + ctx: ctx, + processor: processor, + mu: sync.Mutex{}, + } +} + +func (s *HandlingDispatchStream[T]) Publish(result T) error { + s.mu.Lock() + defer s.mu.Unlock() + + if s.processor == nil { + return nil + } + + return s.processor(result) +} + +func (s *HandlingDispatchStream[T]) Context() context.Context { + return s.ctx +} + // Ensure the streams implement the interface. var _ Stream[any] = &CollectingDispatchStream[any]{} var _ Stream[any] = &WrappedDispatchStream[any]{} diff --git a/internal/graph/lookupsubjects.go b/internal/graph/lookupsubjects.go index 20aa8e3345..ad505d72af 100644 --- a/internal/graph/lookupsubjects.go +++ b/internal/graph/lookupsubjects.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "sync" "github.com/rs/zerolog/log" "github.com/shopspring/decimal" @@ -51,8 +50,8 @@ func (cl *ConcurrentLookupSubjects) LookupSubjects( if req.SubjectRelation.Namespace == req.ResourceRelation.Namespace && req.SubjectRelation.Relation == req.ResourceRelation.Relation { err := stream.Publish(&v1.DispatchLookupSubjectsResponse{ - FoundSubjectIds: req.ResourceIds, - Metadata: emptyMetadata, + FoundSubjects: subjectsForIds(req.ResourceIds), + Metadata: emptyMetadata, }) if err != nil { return err @@ -78,6 +77,16 @@ func (cl *ConcurrentLookupSubjects) LookupSubjects( return cl.lookupViaRewrite(ctx, req, stream, relation.UsersetRewrite) } +func subjectsForIds(subjectIds []string) []*v1.FoundSubject { + foundSubjects := make([]*v1.FoundSubject, 0, len(subjectIds)) + for _, subjectID := range subjectIds { + foundSubjects = append(foundSubjects, &v1.FoundSubject{ + SubjectId: subjectID, + }) + } + return foundSubjects +} + func (cl *ConcurrentLookupSubjects) lookupDirectSubjects( ctx context.Context, req ValidatedLookupSubjectsRequest, @@ -115,8 +124,8 @@ func (cl *ConcurrentLookupSubjects) lookupDirectSubjects( if len(foundSubjectIds) > 0 { err := stream.Publish(&v1.DispatchLookupSubjectsResponse{ - FoundSubjectIds: foundSubjectIds, - Metadata: emptyMetadata, + FoundSubjects: subjectsForIds(foundSubjectIds), + Metadata: emptyMetadata, }) if err != nil { return err @@ -147,8 +156,8 @@ func (cl *ConcurrentLookupSubjects) lookupViaComputed( Ctx: ctx, Processor: func(result *v1.DispatchLookupSubjectsResponse) (*v1.DispatchLookupSubjectsResponse, bool, error) { return &v1.DispatchLookupSubjectsResponse{ - FoundSubjectIds: result.FoundSubjectIds, - Metadata: addCallToResponseMetadata(result.Metadata), + FoundSubjects: result.FoundSubjects, + Metadata: addCallToResponseMetadata(result.Metadata), }, true, nil }, } @@ -309,8 +318,8 @@ func (cl *ConcurrentLookupSubjects) dispatchTo( Ctx: subCtx, Processor: func(result *v1.DispatchLookupSubjectsResponse) (*v1.DispatchLookupSubjectsResponse, bool, error) { return &v1.DispatchLookupSubjectsResponse{ - FoundSubjectIds: result.FoundSubjectIds, - Metadata: addCallToResponseMetadata(result.Metadata), + FoundSubjects: result.FoundSubjects, + Metadata: addCallToResponseMetadata(result.Metadata), }, true, nil }, } @@ -342,47 +351,46 @@ type lookupSubjectsReducer interface { // Union type lookupSubjectsUnion struct { parentStream dispatch.LookupSubjectsStream - encountered *util.Set[string] - mu sync.Mutex + collectors map[int]*dispatch.CollectingDispatchStream[*v1.DispatchLookupSubjectsResponse] } func newLookupSubjectsUnion(parentStream dispatch.LookupSubjectsStream) *lookupSubjectsUnion { return &lookupSubjectsUnion{ parentStream: parentStream, - encountered: util.NewSet[string](), - mu: sync.Mutex{}, + collectors: map[int]*dispatch.CollectingDispatchStream[*v1.DispatchLookupSubjectsResponse]{}, } } func (lsu *lookupSubjectsUnion) ForIndex(ctx context.Context, setOperationIndex int) dispatch.LookupSubjectsStream { - return &dispatch.WrappedDispatchStream[*v1.DispatchLookupSubjectsResponse]{ - Stream: lsu.parentStream, - Ctx: ctx, - Processor: func(result *v1.DispatchLookupSubjectsResponse) (*v1.DispatchLookupSubjectsResponse, bool, error) { - lsu.mu.Lock() - defer lsu.mu.Unlock() - - filtered := make([]string, 0, len(result.FoundSubjectIds)) - for _, subjectID := range result.FoundSubjectIds { - if lsu.encountered.Add(subjectID) { - filtered = append(filtered, subjectID) - } - } + collector := dispatch.NewCollectingDispatchStream[*v1.DispatchLookupSubjectsResponse](ctx) + lsu.collectors[setOperationIndex] = collector + return collector +} - if len(filtered) == 0 { - return nil, false, nil - } +func (lsu *lookupSubjectsUnion) CompletedChildOperations() error { + foundSubjects := util.NewSubjectSet() + metadata := emptyMetadata - return &v1.DispatchLookupSubjectsResponse{ - FoundSubjectIds: filtered, - Metadata: result.Metadata, - }, true, nil - }, + for index := 0; index < len(lsu.collectors); index++ { + collector, ok := lsu.collectors[index] + if !ok { + return fmt.Errorf("missing collector for index %d", index) + } + + for _, result := range collector.Results() { + metadata = combineResponseMetadata(metadata, result.Metadata) + foundSubjects.UnionWith(result.FoundSubjects) + } } -} -func (lsu *lookupSubjectsUnion) CompletedChildOperations() error { - return nil + if foundSubjects.IsEmpty() { + return nil + } + + return lsu.parentStream.Publish(&v1.DispatchLookupSubjectsResponse{ + FoundSubjects: foundSubjects.AsSlice(), + Metadata: metadata, + }) } // Intersection @@ -405,7 +413,7 @@ func (lsi *lookupSubjectsIntersection) ForIndex(ctx context.Context, setOperatio } func (lsi *lookupSubjectsIntersection) CompletedChildOperations() error { - var foundSubjectIds *util.Set[string] + var foundSubjects util.SubjectSet metadata := emptyMetadata for index := 0; index < len(lsi.collectors); index++ { @@ -414,25 +422,25 @@ func (lsi *lookupSubjectsIntersection) CompletedChildOperations() error { return fmt.Errorf("missing collector for index %d", index) } - results := util.NewSet[string]() + results := util.NewSubjectSet() for _, result := range collector.Results() { metadata = combineResponseMetadata(metadata, result.Metadata) - results.Extend(result.FoundSubjectIds) + results.UnionWith(result.FoundSubjects) } if index == 0 { - foundSubjectIds = results + foundSubjects = results } else { - foundSubjectIds.IntersectionDifference(results) - if foundSubjectIds.IsEmpty() { + foundSubjects.IntersectionDifference(results) + if foundSubjects.IsEmpty() { return nil } } } return lsi.parentStream.Publish(&v1.DispatchLookupSubjectsResponse{ - FoundSubjectIds: foundSubjectIds.AsSlice(), - Metadata: metadata, + FoundSubjects: foundSubjects.AsSlice(), + Metadata: metadata, }) } @@ -456,29 +464,29 @@ func (lse *lookupSubjectsExclusion) ForIndex(ctx context.Context, setOperationIn } func (lse *lookupSubjectsExclusion) CompletedChildOperations() error { - var foundSubjectIds *util.Set[string] + var foundSubjects util.SubjectSet metadata := emptyMetadata for index := 0; index < len(lse.collectors); index++ { collector := lse.collectors[index] - results := util.NewSet[string]() + results := util.NewSubjectSet() for _, result := range collector.Results() { metadata = combineResponseMetadata(metadata, result.Metadata) - results.Extend(result.FoundSubjectIds) + results.UnionWith(result.FoundSubjects) } if index == 0 { - foundSubjectIds = results + foundSubjects = results } else { - foundSubjectIds.RemoveAll(results) - if foundSubjectIds.IsEmpty() { + foundSubjects.SubtractAll(results) + if foundSubjects.IsEmpty() { return nil } } } return lse.parentStream.Publish(&v1.DispatchLookupSubjectsResponse{ - FoundSubjectIds: foundSubjectIds.AsSlice(), - Metadata: metadata, + FoundSubjects: foundSubjects.AsSlice(), + Metadata: metadata, }) } diff --git a/internal/membership/foundsubject.go b/internal/membership/foundsubject.go index d9684202f8..fbb3a03d2b 100644 --- a/internal/membership/foundsubject.go +++ b/internal/membership/foundsubject.go @@ -12,7 +12,7 @@ import ( // NewFoundSubject creates a new FoundSubject for a subject and a set of its resources. func NewFoundSubject(subject *core.ObjectAndRelation, resources ...*core.ObjectAndRelation) FoundSubject { - return FoundSubject{subject, tuple.NewONRSet(), tuple.NewONRSet(resources...)} + return FoundSubject{subject, nil, tuple.NewONRSet(resources...)} } // FoundSubject contains a single found subject and all the relationships in which that subject @@ -22,13 +22,24 @@ type FoundSubject struct { subject *core.ObjectAndRelation // excludedSubjects are any subjects excluded. Only should be set if subject is a wildcard. - excludedSubjects *tuple.ONRSet + excludedSubjectIds []string // relations are the relations under which the subject lives that informed the locating // of this subject for the root ONR. relationships *tuple.ONRSet } +// GetSubjectId is named to match the Subject interface for the BaseSubjectSet. +// +//nolint:all +func (fs FoundSubject) GetSubjectId() string { + return fs.subject.ObjectId +} + +func (fs FoundSubject) GetExcludedSubjectIds() []string { + return fs.excludedSubjectIds +} + // Subject returns the Subject of the FoundSubject. func (fs FoundSubject) Subject() *core.ObjectAndRelation { return fs.subject @@ -47,7 +58,16 @@ func (fs FoundSubject) WildcardType() (string, bool) { // If not a wildcard subject, returns false. func (fs FoundSubject) ExcludedSubjectsFromWildcard() ([]*core.ObjectAndRelation, bool) { if fs.subject.ObjectId == tuple.PublicWildcard { - return fs.excludedSubjects.AsSlice(), true + excludedSubjects := make([]*core.ObjectAndRelation, 0, len(fs.excludedSubjectIds)) + for _, excludedID := range fs.excludedSubjectIds { + excludedSubjects = append(excludedSubjects, &core.ObjectAndRelation{ + Namespace: fs.subject.Namespace, + ObjectId: excludedID, + Relation: fs.subject.Relation, + }) + } + + return excludedSubjects, true } return []*core.ObjectAndRelation{}, false @@ -76,67 +96,18 @@ func (fs FoundSubject) ToValidationString() string { return onrString } -// union performs merging of two FoundSubject's with the same subject. -func (fs FoundSubject) union(other FoundSubject) FoundSubject { - if toKey(fs.subject) != toKey(other.subject) { - panic("Got wrong found subject to union") - } - - relationships := fs.relationships.Union(other.relationships) - var excludedSubjects *tuple.ONRSet - - // If a wildcard, then union together excluded subjects. - _, isWildcard := fs.WildcardType() - if isWildcard { - excludedSubjects = fs.excludedSubjects.Union(other.excludedSubjects) - } - - return FoundSubject{ - subject: fs.subject, - excludedSubjects: excludedSubjects, - relationships: relationships, - } -} - -// intersect performs intersection between two FoundSubject's with the same subject. -func (fs FoundSubject) intersect(other FoundSubject) FoundSubject { - if toKey(fs.subject) != toKey(other.subject) { - panic("Got wrong found subject to intersect") - } - - relationships := fs.relationships.Union(other.relationships) - var excludedSubjects *tuple.ONRSet - - // If a wildcard, then union together excluded subjects. - _, isWildcard := fs.WildcardType() - if isWildcard { - excludedSubjects = fs.excludedSubjects.Union(other.excludedSubjects) - } - - return FoundSubject{ - subject: fs.subject, - excludedSubjects: excludedSubjects, - relationships: relationships, - } -} - // FoundSubjects contains the subjects found for a specific ONR. type FoundSubjects struct { // subjects is a map from the Subject ONR (as a string) to the FoundSubject information. - subjects map[string]FoundSubject + subjects TrackingSubjectSet } // ListFound returns a slice of all the FoundSubject's. func (fs FoundSubjects) ListFound() []FoundSubject { - found := []FoundSubject{} - for _, sub := range fs.subjects { - found = append(found, sub) - } - return found + return fs.subjects.ToSlice() } // LookupSubject returns the FoundSubject for a matching subject, if any. func (fs FoundSubjects) LookupSubject(subject *core.ObjectAndRelation) (FoundSubject, bool) { - found, ok := fs.subjects[toKey(subject)] - return found, ok + return fs.subjects.Get(subject) } diff --git a/internal/membership/foundsubject_test.go b/internal/membership/foundsubject_test.go index 8334af9442..b573b2cab9 100644 --- a/internal/membership/foundsubject_test.go +++ b/internal/membership/foundsubject_test.go @@ -22,29 +22,20 @@ func TestToValidationString(t *testing.T) { }, { "with exclusion", - fs("user", "*", "...", ONR("user", "user1", "...")), + fs("user", "*", "...", "user1"), "user:* - {user:user1}", }, { "with some exclusion", fs("user", "*", "...", - ONR("user", "user1", "..."), - ONR("user", "user2", "..."), - ONR("user", "user3", "..."), - ONR("user", "user4", "..."), - ONR("user", "user5", "..."), + "user1", "user2", "user3", "user4", "user5", ), "user:* - {user:user1, user:user2, user:user3, user:user4, user:user5}", }, { "with many exclusion", fs("user", "*", "...", - ONR("user", "user1", "..."), - ONR("user", "user2", "..."), - ONR("user", "user3", "..."), - ONR("user", "user4", "..."), - ONR("user", "user5", "..."), - ONR("user", "user6", "..."), + "user1", "user2", "user3", "user4", "user5", "user6", ), "user:* - {user:user1, user:user2, user:user3, user:user4, user:user5, user:user6}", }, diff --git a/internal/membership/membership.go b/internal/membership/membership.go index d9f86a6c9c..5de467dc1d 100644 --- a/internal/membership/membership.go +++ b/internal/membership/membership.go @@ -51,11 +51,11 @@ func (ms *Set) AddExpansion(onr *core.ObjectAndRelation, expansion *core.Relatio } // AccessibleExpansionSubjects returns a TrackingSubjectSet representing the set of accessible subjects in the expansion. -func AccessibleExpansionSubjects(treeNode *core.RelationTupleTreeNode) (TrackingSubjectSet, error) { +func AccessibleExpansionSubjects(treeNode *core.RelationTupleTreeNode) (*TrackingSubjectSet, error) { return populateFoundSubjects(treeNode.Expanded, treeNode) } -func populateFoundSubjects(rootONR *core.ObjectAndRelation, treeNode *core.RelationTupleTreeNode) (TrackingSubjectSet, error) { +func populateFoundSubjects(rootONR *core.ObjectAndRelation, treeNode *core.RelationTupleTreeNode) (*TrackingSubjectSet, error) { resource := rootONR if treeNode.Expanded != nil { resource = treeNode.Expanded diff --git a/internal/membership/membership_test.go b/internal/membership/membership_test.go index acdb41f0af..fdb860e59e 100644 --- a/internal/membership/membership_test.go +++ b/internal/membership/membership_test.go @@ -98,6 +98,71 @@ func TestMembershipSetIntersectionBasic(t *testing.T) { verifySubjects(t, require, fso, "user:legal") } +func TestMembershipSetIntersectionWithDifferentTypesOneMissingLeft(t *testing.T) { + require := require.New(t) + ms := NewMembershipSet() + + intersection := graph.Intersection(ONR("folder", "company", "viewer"), + graph.Leaf(_this, + (ONR("user", "legal", "...")), + (ONR("folder", "foobar", "...")), + ), + graph.Leaf(_this, + (ONR("user", "owner", "...")), + (ONR("user", "legal", "...")), + ), + ) + + fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection) + require.True(ok) + require.NoError(err) + verifySubjects(t, require, fso, "user:legal") +} + +func TestMembershipSetIntersectionWithDifferentTypesOneMissingRight(t *testing.T) { + require := require.New(t) + ms := NewMembershipSet() + + intersection := graph.Intersection(ONR("folder", "company", "viewer"), + graph.Leaf(_this, + (ONR("user", "legal", "...")), + ), + graph.Leaf(_this, + (ONR("user", "owner", "...")), + (ONR("user", "legal", "...")), + (ONR("folder", "foobar", "...")), + ), + ) + + fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection) + require.True(ok) + require.NoError(err) + verifySubjects(t, require, fso, "user:legal") +} + +func TestMembershipSetIntersectionWithDifferentTypes(t *testing.T) { + require := require.New(t) + ms := NewMembershipSet() + + intersection := graph.Intersection(ONR("folder", "company", "viewer"), + graph.Leaf(_this, + (ONR("user", "legal", "...")), + (ONR("folder", "foobar", "...")), + (ONR("folder", "barbaz", "...")), + ), + graph.Leaf(_this, + (ONR("user", "owner", "...")), + (ONR("user", "legal", "...")), + (ONR("folder", "barbaz", "...")), + ), + ) + + fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection) + require.True(ok) + require.NoError(err) + verifySubjects(t, require, fso, "folder:barbaz", "user:legal") +} + func TestMembershipSetExclusion(t *testing.T) { require := require.New(t) ms := NewMembershipSet() diff --git a/internal/membership/trackingsubjectset.go b/internal/membership/trackingsubjectset.go index fe501fbb2d..e55e71f3dc 100644 --- a/internal/membership/trackingsubjectset.go +++ b/internal/membership/trackingsubjectset.go @@ -2,171 +2,163 @@ package membership import ( "fmt" + "strings" core "github.com/authzed/spicedb/pkg/proto/core/v1" - "github.com/authzed/spicedb/pkg/tuple" + "github.com/authzed/spicedb/internal/util" ) -func isWildcard(subject *core.ObjectAndRelation) bool { - return subject.ObjectId == tuple.PublicWildcard -} - // TrackingSubjectSet defines a set that tracks accessible subjects and their associated // relationships. // // NOTE: This is designed solely for the developer API and testing and should *not* be used in any // performance sensitive code. -// -// NOTE: Unlike a traditional set, unions between wildcards and a concrete subject will result -// in *both* being present in the set, to maintain the proper relationship tracking and reporting -// of concrete subjects. -// -// TODO(jschorr): Once we have stable generics support, break into a standard SubjectSet and -// a tracking variant built on top of it. -type TrackingSubjectSet map[string]FoundSubject +type TrackingSubjectSet struct { + setByType map[string]util.BaseSubjectSet[FoundSubject] +} // NewTrackingSubjectSet creates a new TrackingSubjectSet, with optional initial subjects. -func NewTrackingSubjectSet(subjects ...FoundSubject) TrackingSubjectSet { - var toReturn TrackingSubjectSet = make(map[string]FoundSubject) - toReturn.Add(subjects...) - return toReturn +func NewTrackingSubjectSet(subjects ...FoundSubject) *TrackingSubjectSet { + tss := &TrackingSubjectSet{ + setByType: map[string]util.BaseSubjectSet[FoundSubject]{}, + } + for _, subject := range subjects { + tss.Add(subject) + } + return tss } // AddFrom adds the subjects found in the other set to this set. -func (tss TrackingSubjectSet) AddFrom(otherSet TrackingSubjectSet) { - for _, value := range otherSet { - tss.Add(value) +func (tss *TrackingSubjectSet) AddFrom(otherSet *TrackingSubjectSet) { + for key, oss := range otherSet.setByType { + tss.getSetForKey(key).UnionWithSet(oss) } } // RemoveFrom removes any subjects found in the other set from this set. -func (tss TrackingSubjectSet) RemoveFrom(otherSet TrackingSubjectSet) { - for _, otherSAR := range otherSet { - tss.Remove(otherSAR.subject) +func (tss *TrackingSubjectSet) RemoveFrom(otherSet *TrackingSubjectSet) { + for key, oss := range otherSet.setByType { + tss.getSetForKey(key).SubtractAll(oss) } } // Add adds the given subjects to this set. -func (tss TrackingSubjectSet) Add(subjectsAndResources ...FoundSubject) { - tss.AddWithResources(subjectsAndResources, nil) -} - -// AddWithResources adds the given subjects to this set, with the additional resources appended -// for each subject to be included in their relationships. -func (tss TrackingSubjectSet) AddWithResources(subjectsAndResources []FoundSubject, additionalResources *tuple.ONRSet) { - for _, sar := range subjectsAndResources { - found, ok := tss[toKey(sar.subject)] - if ok { - tss[toKey(sar.subject)] = found.union(sar) - } else { - tss[toKey(sar.subject)] = sar - } +func (tss *TrackingSubjectSet) Add(subjectsAndResources ...FoundSubject) { + for _, fs := range subjectsAndResources { + tss.getSet(fs).Add(fs) } } -// Get returns the found subject in the set, if any. -func (tss TrackingSubjectSet) Get(subject *core.ObjectAndRelation) (FoundSubject, bool) { - found, ok := tss[toKey(subject)] - return found, ok +func keyFor(fs FoundSubject) string { + return fmt.Sprintf("%s#%s", fs.subject.Namespace, fs.subject.Relation) } -// Contains returns true if the set contains the given subject. -func (tss TrackingSubjectSet) Contains(subject *core.ObjectAndRelation) bool { - _, ok := tss[toKey(subject)] - return ok +func (tss *TrackingSubjectSet) getSetForKey(key string) util.BaseSubjectSet[FoundSubject] { + if existing, ok := tss.setByType[key]; ok { + return existing + } + + parts := strings.Split(key, "#") + + created := util.NewBaseSubjectSet[FoundSubject]( + func(subjectID string, excludedSubjectIDs []string, sources ...FoundSubject) FoundSubject { + fs := NewFoundSubject(&core.ObjectAndRelation{ + Namespace: parts[0], + ObjectId: subjectID, + Relation: parts[1], + }) + fs.excludedSubjectIds = excludedSubjectIDs + for _, source := range sources { + fs.relationships.UpdateFrom(source.relationships) + } + return fs + }, + func(existing FoundSubject, added FoundSubject) FoundSubject { + fs := NewFoundSubject(existing.subject) + fs.excludedSubjectIds = existing.excludedSubjectIds + fs.relationships = existing.relationships.Union(added.relationships) + return fs + }, + ) + tss.setByType[key] = created + return created } -// removeExact removes the given subject(s) from the set. If the subject is a wildcard, only -// the exact matching wildcard will be removed. -func (tss TrackingSubjectSet) removeExact(subjects ...*core.ObjectAndRelation) { - for _, subject := range subjects { - delete(tss, toKey(subject)) - } +func (tss *TrackingSubjectSet) getSet(fs FoundSubject) util.BaseSubjectSet[FoundSubject] { + fsKey := keyFor(fs) + return tss.getSetForKey(fsKey) } -// Remove removes the given subject(s) from the set. If the subject is a wildcard, all matching -// subjects are removed. If the subject matches a wildcard in the existing set, then it is added -// to that wildcard as an exclusion. -func (tss TrackingSubjectSet) Remove(subjects ...*core.ObjectAndRelation) { - for _, subject := range subjects { - delete(tss, toKey(subject)) - - // Delete any entries matching the wildcard, if applicable. - if isWildcard(subject) { - // Remove any subjects matching the type. - for key := range tss { - current := fromKey(key) - if current.Namespace == subject.Namespace { - delete(tss, key) - } - } - } else { - // Check for any wildcards matching and, if found, add to the exclusion. - for _, existing := range tss { - wildcardType, ok := existing.WildcardType() - if ok && wildcardType == subject.Namespace { - existing.excludedSubjects.Add(subject) - } - } - } +// Get returns the found subject in the set, if any. +func (tss *TrackingSubjectSet) Get(subject *core.ObjectAndRelation) (FoundSubject, bool) { + set, ok := tss.setByType[fmt.Sprintf("%s#%s", subject.Namespace, subject.Relation)] + if !ok { + return FoundSubject{}, false } + + return set.Get(subject.ObjectId) } -// WithType returns any subjects in the set with the given object type. -func (tss TrackingSubjectSet) WithType(objectType string) []FoundSubject { - toReturn := make([]FoundSubject, 0, len(tss)) - for _, current := range tss { - if current.subject.Namespace == objectType { - toReturn = append(toReturn, current) - } - } - return toReturn +// Contains returns true if the set contains the given subject. +func (tss *TrackingSubjectSet) Contains(subject *core.ObjectAndRelation) bool { + _, ok := tss.Get(subject) + return ok } // Exclude returns a new set that contains the items in this set minus those in the other set. -func (tss TrackingSubjectSet) Exclude(otherSet TrackingSubjectSet) TrackingSubjectSet { +func (tss *TrackingSubjectSet) Exclude(otherSet *TrackingSubjectSet) *TrackingSubjectSet { newSet := NewTrackingSubjectSet() - newSet.AddFrom(tss) - newSet.RemoveFrom(otherSet) + + for key, bss := range tss.setByType { + cloned := bss.Clone() + if oss, ok := otherSet.setByType[key]; ok { + cloned.SubtractAll(oss) + } + + newSet.setByType[key] = cloned + } + return newSet } // Intersect returns a new set that contains the items in this set *and* the other set. Note that // if wildcard is found in *both* sets, it will be returned *along* with any concrete subjects found // on the other side of the intersection. -func (tss TrackingSubjectSet) Intersect(otherSet TrackingSubjectSet) TrackingSubjectSet { +func (tss *TrackingSubjectSet) Intersect(otherSet *TrackingSubjectSet) *TrackingSubjectSet { newSet := NewTrackingSubjectSet() - for _, current := range tss { - // Add directly if shared by both. - other, ok := otherSet.Get(current.subject) - if ok { - newSet.Add(current.intersect(other)) - } - // If the current is a wildcard, and add any matching. - if isWildcard(current.subject) { - newSet.AddWithResources(otherSet.WithType(current.subject.Namespace), current.relationships) + for key, bss := range tss.setByType { + if oss, ok := otherSet.setByType[key]; ok { + cloned := bss.Clone() + cloned.IntersectionDifference(oss) + newSet.setByType[key] = cloned } } - for _, current := range otherSet { - // If the current is a wildcard, add any matching. - if isWildcard(current.subject) { - newSet.AddWithResources(tss.WithType(current.subject.Namespace), current.relationships) + return newSet +} + +// removeExact removes the given subject(s) from the set. If the subject is a wildcard, only +// the exact matching wildcard will be removed. +func (tss TrackingSubjectSet) removeExact(subjects ...*core.ObjectAndRelation) { + for _, subject := range subjects { + if set, ok := tss.setByType[fmt.Sprintf("%s#%s", subject.Namespace, subject.Relation)]; ok { + set.UnsafeRemoveExact(FoundSubject{ + subject: subject, + }) } } - - return newSet } // ToSlice returns a slice of all subjects found in the set. func (tss TrackingSubjectSet) ToSlice() []FoundSubject { - toReturn := make([]FoundSubject, 0, len(tss)) - for _, current := range tss { - toReturn = append(toReturn, current) + subjects := []FoundSubject{} + for _, bss := range tss.setByType { + subjects = append(subjects, bss.AsSlice()...) } - return toReturn + + return subjects } // ToFoundSubjects returns the set as a FoundSubjects struct. @@ -174,12 +166,12 @@ func (tss TrackingSubjectSet) ToFoundSubjects() FoundSubjects { return FoundSubjects{tss} } -func toKey(subject *core.ObjectAndRelation) string { - return fmt.Sprintf("%s %s %s", subject.Namespace, subject.ObjectId, subject.Relation) -} - -func fromKey(key string) *core.ObjectAndRelation { - subject := &core.ObjectAndRelation{} - fmt.Sscanf(key, "%s %s %s", &subject.Namespace, &subject.ObjectId, &subject.Relation) - return subject +// IsEmpty returns true if the tracking subject set is empty. +func (tss TrackingSubjectSet) IsEmpty() bool { + for _, bss := range tss.setByType { + if !bss.IsEmpty() { + return false + } + } + return true } diff --git a/internal/membership/trackingsubjectset_test.go b/internal/membership/trackingsubjectset_test.go index f04e87c935..c5d627b2d0 100644 --- a/internal/membership/trackingsubjectset_test.go +++ b/internal/membership/trackingsubjectset_test.go @@ -7,10 +7,11 @@ import ( core "github.com/authzed/spicedb/pkg/proto/core/v1" + "github.com/authzed/spicedb/internal/util" "github.com/authzed/spicedb/pkg/tuple" ) -func set(subjects ...*core.ObjectAndRelation) TrackingSubjectSet { +func set(subjects ...*core.ObjectAndRelation) *TrackingSubjectSet { newSet := NewTrackingSubjectSet() for _, subject := range subjects { newSet.Add(NewFoundSubject(subject)) @@ -18,15 +19,16 @@ func set(subjects ...*core.ObjectAndRelation) TrackingSubjectSet { return newSet } -func union(firstSet TrackingSubjectSet, sets ...TrackingSubjectSet) TrackingSubjectSet { +func union(firstSet *TrackingSubjectSet, sets ...*TrackingSubjectSet) *TrackingSubjectSet { current := firstSet for _, set := range sets { current.AddFrom(set) } + return current } -func intersect(firstSet TrackingSubjectSet, sets ...TrackingSubjectSet) TrackingSubjectSet { +func intersect(firstSet *TrackingSubjectSet, sets ...*TrackingSubjectSet) *TrackingSubjectSet { current := firstSet for _, set := range sets { current = current.Intersect(set) @@ -34,7 +36,7 @@ func intersect(firstSet TrackingSubjectSet, sets ...TrackingSubjectSet) Tracking return current } -func exclude(firstSet TrackingSubjectSet, sets ...TrackingSubjectSet) TrackingSubjectSet { +func subtract(firstSet *TrackingSubjectSet, sets ...*TrackingSubjectSet) *TrackingSubjectSet { current := firstSet for _, set := range sets { current = current.Exclude(set) @@ -42,18 +44,18 @@ func exclude(firstSet TrackingSubjectSet, sets ...TrackingSubjectSet) TrackingSu return current } -func fs(subjectType string, subjectID string, subjectRel string, excludedSubjects ...*core.ObjectAndRelation) FoundSubject { +func fs(subjectType string, subjectID string, subjectRel string, excludedSubjectIDs ...string) FoundSubject { return FoundSubject{ - subject: ONR(subjectType, subjectID, subjectRel), - excludedSubjects: tuple.NewONRSet(excludedSubjects...), - relationships: tuple.NewONRSet(), + subject: ONR(subjectType, subjectID, subjectRel), + excludedSubjectIds: excludedSubjectIDs, + relationships: tuple.NewONRSet(), } } func TestTrackingSubjectSet(t *testing.T) { testCases := []struct { name string - set TrackingSubjectSet + set *TrackingSubjectSet expected []FoundSubject }{ { @@ -108,7 +110,7 @@ func TestTrackingSubjectSet(t *testing.T) { }, { "simple exclusion", - exclude( + subtract( set( (ONR("user", "user1", "...")), (ONR("user", "user2", "...")), @@ -120,7 +122,7 @@ func TestTrackingSubjectSet(t *testing.T) { }, { "empty exclusion", - exclude( + subtract( set( (ONR("user", "user1", "...")), (ONR("user", "user2", "...")), @@ -158,7 +160,7 @@ func TestTrackingSubjectSet(t *testing.T) { }, { "wildcard left side exclusion", - exclude( + subtract( set( (ONR("user", "*", "...")), (ONR("user", "user2", "...")), @@ -166,13 +168,13 @@ func TestTrackingSubjectSet(t *testing.T) { set(ONR("user", "user1", "...")), ), []FoundSubject{ - fs("user", "*", "...", ONR("user", "user1", "...")), + fs("user", "*", "...", "user1"), fs("user", "user2", "..."), }, }, { "wildcard right side exclusion", - exclude( + subtract( set( (ONR("user", "user2", "...")), ), @@ -182,19 +184,19 @@ func TestTrackingSubjectSet(t *testing.T) { }, { "wildcard right side concrete exclusion", - exclude( + subtract( set( (ONR("user", "*", "...")), ), set(ONR("user", "user1", "...")), ), []FoundSubject{ - fs("user", "*", "...", ONR("user", "user1", "...")), + fs("user", "*", "...", "user1"), }, }, { "wildcard both sides exclusion", - exclude( + subtract( set( (ONR("user", "user2", "...")), (ONR("user", "*", "...")), @@ -249,70 +251,72 @@ func TestTrackingSubjectSet(t *testing.T) { { "wildcard with exclusions union", union( - NewTrackingSubjectSet(fs("user", "*", "...", ONR("user", "user1", "..."))), - NewTrackingSubjectSet(fs("user", "*", "...", ONR("user", "user2", "..."))), + NewTrackingSubjectSet(fs("user", "*", "...", "user1")), + NewTrackingSubjectSet(fs("user", "*", "...", "user2")), ), []FoundSubject{ - fs("user", "*", "...", ONR("user", "user1", "..."), ONR("user", "user2", "...")), + fs("user", "*", "..."), }, }, { "wildcard with exclusions intersection", intersect( - NewTrackingSubjectSet(fs("user", "*", "...", ONR("user", "user1", "..."))), - NewTrackingSubjectSet(fs("user", "*", "...", ONR("user", "user2", "..."))), + NewTrackingSubjectSet(fs("user", "*", "...", "user1")), + NewTrackingSubjectSet(fs("user", "*", "...", "user2")), ), []FoundSubject{ - fs("user", "*", "...", ONR("user", "user1", "..."), ONR("user", "user2", "...")), + fs("user", "*", "...", "user1", "user2"), }, }, { - "wildcard with exclusions exclusion", - exclude( + "wildcard with exclusions over subtraction", + subtract( NewTrackingSubjectSet( - fs("user", "*", "...", ONR("user", "user1", "...")), + fs("user", "*", "...", "user1"), ), - NewTrackingSubjectSet(fs("user", "*", "...", ONR("user", "user2", "..."))), + NewTrackingSubjectSet(fs("user", "*", "...", "user2")), ), - []FoundSubject{}, + []FoundSubject{ + fs("user", "user2", "..."), + }, }, { "wildcard with exclusions excluded user added", - exclude( + subtract( NewTrackingSubjectSet( - fs("user", "*", "...", ONR("user", "user1", "...")), + fs("user", "*", "...", "user1"), ), NewTrackingSubjectSet(fs("user", "user2", "...")), ), []FoundSubject{ - fs("user", "*", "...", ONR("user", "user1", "..."), ONR("user", "user2", "...")), + fs("user", "*", "...", "user1", "user2"), }, }, { "wildcard multiple exclusions", - exclude( + subtract( NewTrackingSubjectSet( - fs("user", "*", "...", ONR("user", "user1", "...")), + fs("user", "*", "...", "user1"), ), NewTrackingSubjectSet(fs("user", "user2", "...")), NewTrackingSubjectSet(fs("user", "user3", "...")), ), []FoundSubject{ - fs("user", "*", "...", ONR("user", "user1", "..."), ONR("user", "user2", "..."), ONR("user", "user3", "...")), + fs("user", "*", "...", "user1", "user2", "user3"), }, }, { "intersection of exclusions", intersect( NewTrackingSubjectSet( - fs("user", "*", "...", ONR("user", "user1", "...")), + fs("user", "*", "...", "user1"), ), NewTrackingSubjectSet( - fs("user", "*", "...", ONR("user", "user2", "...")), + fs("user", "*", "...", "user2"), ), ), []FoundSubject{ - fs("user", "*", "...", ONR("user", "user1", "..."), ONR("user", "user2", "...")), + fs("user", "*", "...", "user1", "user2"), }, }, } @@ -320,24 +324,54 @@ func TestTrackingSubjectSet(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { require := require.New(t) - for _, fs := range tc.expected { _, isWildcard := fs.WildcardType() if isWildcard { found, ok := tc.set.Get(fs.subject) require.True(ok, "missing expected subject %s", fs.subject) - expectedExcluded := fs.excludedSubjects.AsSlice() - foundExcluded := found.excludedSubjects.AsSlice() - require.Len(fs.excludedSubjects.Subtract(found.excludedSubjects).AsSlice(), 0, "mismatch on excluded subjects on %s: expected: %s, found: %s", fs.subject, expectedExcluded, foundExcluded) - require.Len(found.excludedSubjects.Subtract(fs.excludedSubjects).AsSlice(), 0, "mismatch on excluded subjects on %s: expected: %s, found: %s", fs.subject, expectedExcluded, foundExcluded) + expectedExcluded := util.NewSet[string](fs.excludedSubjectIds...) + foundExcluded := util.NewSet[string](found.excludedSubjectIds...) + require.Len(expectedExcluded.Subtract(foundExcluded).AsSlice(), 0, "mismatch on excluded subjects on %s: expected: %s, found: %s", fs.subject, expectedExcluded, foundExcluded) + require.Len(foundExcluded.Subtract(expectedExcluded).AsSlice(), 0, "mismatch on excluded subjects on %s: expected: %s, found: %s", fs.subject, expectedExcluded, foundExcluded) } else { require.True(tc.set.Contains(fs.subject), "missing expected subject %s", fs.subject) } tc.set.removeExact(fs.subject) } - require.Len(tc.set, 0) + require.True(tc.set.IsEmpty()) }) } } + +func TestTrackingSubjectSetResourceTracking(t *testing.T) { + tss := NewTrackingSubjectSet() + tss.Add(NewFoundSubject(ONR("user", "tom", "..."), ONR("resource", "foo", "viewer"))) + tss.Add(NewFoundSubject(ONR("user", "tom", "..."), ONR("resource", "bar", "viewer"))) + + found, ok := tss.Get(ONR("user", "tom", "...")) + require.True(t, ok) + require.Equal(t, 2, len(found.Relationships())) + + sss := NewTrackingSubjectSet() + sss.Add(NewFoundSubject(ONR("user", "tom", "..."), ONR("resource", "baz", "viewer"))) + + intersection := tss.Intersect(sss) + found, ok = intersection.Get(ONR("user", "tom", "...")) + require.True(t, ok) + require.Equal(t, 3, len(found.Relationships())) +} + +func TestTrackingSubjectSetResourceTrackingWithWildcard(t *testing.T) { + tss := NewTrackingSubjectSet() + tss.Add(NewFoundSubject(ONR("user", "tom", "..."), ONR("resource", "foo", "viewer"))) + + sss := NewTrackingSubjectSet() + sss.Add(NewFoundSubject(ONR("user", "*", "..."), ONR("resource", "baz", "viewer"))) + + intersection := tss.Intersect(sss) + found, ok := intersection.Get(ONR("user", "tom", "...")) + require.True(t, ok) + require.Equal(t, 1, len(found.Relationships())) +} diff --git a/internal/services/consistency_test.go b/internal/services/consistency_test.go index 0f97c149bf..33b471a124 100644 --- a/internal/services/consistency_test.go +++ b/internal/services/consistency_test.go @@ -343,8 +343,11 @@ func runConsistencyTests(t *testing.T, validateExpansionSubjects(t, ds, vctx) // For each relation in each namespace, for each user, collect the objects accessible - // to that user and then verify the lookup returns the same set of objects. - validateLookup(t, vctx) + // to that user and then verify the lookup resources returns the same set of objects. + validateLookupResources(t, vctx) + + // For each object accessible, validate that the subjects that can access it are found. + validateLookupSubject(t, vctx) // Run the developer APIs over the full set of context and ensure they also return the expected information. store := v0svc.NewInMemoryShareStore("flavored") @@ -435,9 +438,10 @@ func validateValidation(t *testing.T, dev v0.DeveloperServiceServer, reqContext require.True(t, (vctx.accessibilitySet.GetIsMember(onr, subjectWithExceptions.Subject) == isMember || vctx.accessibilitySet.GetIsMember(onr, subjectWithExceptions.Subject) == isWildcard), - "Generated expected relations returned inaccessible member %s for %s", + "Generated expected relations returned inaccessible member %s for %s in `%s`", tuple.StringONR(subjectWithExceptions.Subject), - tuple.StringONR(onr)) + tuple.StringONR(onr), + updatedValidationYaml) } } @@ -523,7 +527,7 @@ func validateEditChecks(t *testing.T, dev v0.DeveloperServiceServer, reqContext } } -func validateLookup(t *testing.T, vctx *validationContext) { +func validateLookupResources(t *testing.T, vctx *validationContext) { for _, nsDef := range vctx.fullyResolved.NamespaceDefinitions { for _, relation := range nsDef.Relation { for _, subject := range vctx.subjectsNoWildcard.AsSlice() { @@ -532,7 +536,7 @@ func validateLookup(t *testing.T, vctx *validationContext) { Relation: relation.Name, } - t.Run(fmt.Sprintf("lookup_%s_%s_to_%s_%s_%s", objectRelation.Namespace, objectRelation.Relation, subject.Namespace, subject.ObjectId, subject.Relation), func(t *testing.T) { + t.Run(fmt.Sprintf("lookupresources_%s_%s_to_%s_%s_%s", objectRelation.Namespace, objectRelation.Relation, subject.Namespace, subject.ObjectId, subject.Relation), func(t *testing.T) { vrequire := require.New(t) accessibleObjectIds := vctx.accessibilitySet.AccessibleObjectIDs(objectRelation.Namespace, objectRelation.Relation, subject) @@ -583,6 +587,79 @@ func validateLookup(t *testing.T, vctx *validationContext) { } } +func validateLookupSubject(t *testing.T, vctx *validationContext) { + for _, nsDef := range vctx.fullyResolved.NamespaceDefinitions { + allObjectIds, ok := vctx.objectsPerNamespace.Get(nsDef.Name) + if !ok { + continue + } + + for _, relation := range nsDef.Relation { + for _, objectID := range allObjectIds { + objectIDStr := objectID.(string) + + accessibleSubjectsByType := vctx.accessibilitySet.AccessibleSubjectsByType(nsDef.Name, relation.Name, objectIDStr) + accessibleSubjectsByType.ForEachType(func(subjectType *core.RelationReference, expectedObjectIds []string) { + t.Run(fmt.Sprintf("lookupsubjects_%s_%s_%s_to_%s_%s", nsDef.Name, relation.Name, objectID, subjectType.Namespace, subjectType.Relation), func(t *testing.T) { + vrequire := require.New(t) + + // Perform a lookup call and ensure it returns the at least the same set of object IDs. + resource := &core.ObjectAndRelation{ + Namespace: nsDef.Name, + ObjectId: objectIDStr, + Relation: relation.Name, + } + resolvedObjectIds, err := vctx.tester.LookupSubjects(context.Background(), resource, subjectType, vctx.revision) + vrequire.NoError(err) + + sort.Strings(expectedObjectIds) + sort.Strings(resolvedObjectIds) + + // Ensure the object IDs match. + for _, expectedObjectID := range expectedObjectIds { + vrequire.True( + contains(resolvedObjectIds, expectedObjectID), + "Object `%s` missing in lookup subjects results for subjects of %s under %s: Expected: %v. Found: %v", + expectedObjectID, + tuple.StringRR(subjectType), + tuple.StringONR(resource), + expectedObjectIds, + resolvedObjectIds, + ) + } + + // Ensure that every returned object Checks. + for _, resolvedObjectID := range resolvedObjectIds { + if resolvedObjectID == tuple.PublicWildcard { + continue + } + + subject := &core.ObjectAndRelation{ + Namespace: subjectType.Namespace, + ObjectId: resolvedObjectID, + Relation: subjectType.Relation, + } + isMember, err := vctx.tester.Check(context.Background(), + resource, + subject, + vctx.revision, + ) + vrequire.NoError(err) + vrequire.True( + isMember, + "Found Check failure for resource %s and subject %s", + nsDef.Name, + tuple.StringONR(resource), + tuple.StringONR(subject), + ) + } + }) + }) + } + } + } +} + func validateExpansion(t *testing.T, vctx *validationContext) { for _, nsDef := range vctx.fullyResolved.NamespaceDefinitions { allObjectIds, ok := vctx.objectsPerNamespace.Get(nsDef.Name) @@ -803,8 +880,23 @@ func (rs *accessibilitySet) AccessibleObjectIDs(namespaceName string, relationNa return accessibleObjectIDs } +// AccessibleSubjects returns the set of subjects with accessible for the given object on the given relation on the namespace +func (rs *accessibilitySet) AccessibleSubjectsByType(namespaceName string, relationName string, objectIDStr string) *tuple.ONRByTypeSet { + accessibleSubjects := tuple.NewONRByTypeSet() + for _, result := range rs.results { + if result.isMember == isNotMember || result.isMember == isWildcard || result.isMember == isMemberViaWildcard { + continue + } + + if result.object.Namespace == namespaceName && result.object.Relation == relationName && result.object.ObjectId == objectIDStr { + accessibleSubjects.Add(result.subject) + } + } + return accessibleSubjects +} + // AccessibleTerminalSubjects returns the set of terminal subjects with accessible for the given object on the given relation on the namespace -func (rs *accessibilitySet) AccessibleTerminalSubjects(namespaceName string, relationName string, objectIDStr string) membership.TrackingSubjectSet { +func (rs *accessibilitySet) AccessibleTerminalSubjects(namespaceName string, relationName string, objectIDStr string) *membership.TrackingSubjectSet { accessibleSubjects := membership.NewTrackingSubjectSet() for _, result := range rs.results { if result.isMember == isNotMember || result.isMember == isWildcard { diff --git a/internal/services/dispatch/v1/acl.go b/internal/services/dispatch/v1/acl.go index a7377c9770..0dfc8285b4 100644 --- a/internal/services/dispatch/v1/acl.go +++ b/internal/services/dispatch/v1/acl.go @@ -56,6 +56,14 @@ func (ds *dispatchServer) DispatchReachableResources( dispatch.WrapGRPCStream[*dispatchv1.DispatchReachableResourcesResponse](resp)) } +func (ds *dispatchServer) DispatchLookupSubjects( + req *dispatchv1.DispatchLookupSubjectsRequest, + resp dispatchv1.DispatchService_DispatchLookupSubjectsServer, +) error { + return ds.localDispatch.DispatchLookupSubjects(req, + dispatch.WrapGRPCStream[*dispatchv1.DispatchLookupSubjectsResponse](resp)) +} + func (ds *dispatchServer) Close() error { return nil } diff --git a/internal/services/servicetester_test.go b/internal/services/servicetester_test.go index 1f91c632fc..c6509f0d06 100644 --- a/internal/services/servicetester_test.go +++ b/internal/services/servicetester_test.go @@ -23,6 +23,7 @@ type serviceTester interface { Write(ctx context.Context, relationship *core.RelationTuple) error Read(ctx context.Context, namespaceName string, atRevision decimal.Decimal) ([]*core.RelationTuple, error) Lookup(ctx context.Context, resourceRelation *core.RelationReference, subject *core.ObjectAndRelation, atRevision decimal.Decimal) ([]string, error) + LookupSubjects(ctx context.Context, resource *core.ObjectAndRelation, subjectRelation *core.RelationReference, atRevision decimal.Decimal) ([]string, error) } func optionalizeRelation(relation string) string { @@ -189,3 +190,40 @@ func (v1st v1ServiceTester) Lookup(ctx context.Context, resourceRelation *core.R sort.Strings(objectIds) return objectIds, nil } + +func (v1st v1ServiceTester) LookupSubjects(ctx context.Context, resource *core.ObjectAndRelation, subjectRelation *core.RelationReference, atRevision decimal.Decimal) ([]string, error) { + lookupResp, err := v1st.permClient.LookupSubjects(context.Background(), &v1.LookupSubjectsRequest{ + Resource: &v1.ObjectReference{ + ObjectType: resource.Namespace, + ObjectId: resource.ObjectId, + }, + Permission: resource.Relation, + SubjectObjectType: subjectRelation.Namespace, + OptionalSubjectRelation: optionalizeRelation(subjectRelation.Relation), + Consistency: &v1.Consistency{ + Requirement: &v1.Consistency_AtLeastAsFresh{ + AtLeastAsFresh: zedtoken.NewFromRevision(atRevision), + }, + }, + }) + if err != nil { + return nil, err + } + + var objectIds []string + for { + resp, err := lookupResp.Recv() + if errors.Is(err, io.EOF) { + break + } + + if err != nil { + return nil, err + } + + objectIds = append(objectIds, resp.SubjectObjectId) + } + + sort.Strings(objectIds) + return objectIds, nil +} diff --git a/internal/services/testconfigs/nestedwilcardexclusions.yaml b/internal/services/testconfigs/nestedwilcardexclusions.yaml new file mode 100644 index 0000000000..f6508594ac --- /dev/null +++ b/internal/services/testconfigs/nestedwilcardexclusions.yaml @@ -0,0 +1,21 @@ +--- +schema: >- + definition test/user {} + + definition test/resource { + relation viewer: test/user | test/user:* + relation maybebanned: test/user | test/user:* + relation notreallybanned: test/user | test/user:* + + permission possiblybanned = maybebanned - notreallybanned + permission view = viewer - possiblybanned + } +relationships: | + test/resource:first#viewer@test/user:* + test/resource:first#maybebanned@test/user:* + test/resource:first#notreallybanned@test/user:sarah +assertions: + assertTrue: + - "test/resource:first#view@test/user:sarah" + assertFalse: + - "test/resource:first#view@test/user:tom" diff --git a/internal/services/testconfigs/wildcardwithnestedexclusions.yaml b/internal/services/testconfigs/wildcardwithnestedexclusions.yaml new file mode 100644 index 0000000000..ef2548a2e6 --- /dev/null +++ b/internal/services/testconfigs/wildcardwithnestedexclusions.yaml @@ -0,0 +1,22 @@ +--- +schema: >- + definition test/user {} + + definition test/resource { + relation viewer: test/user | test/user:* + relation maybebanned: test/user + relation notreallybanned: test/user + + permission possiblybanned = maybebanned - notreallybanned + permission view = viewer - possiblybanned + } +relationships: | + test/resource:first#viewer@test/user:* + test/resource:first#maybebanned@test/user:tom + test/resource:first#maybebanned@test/user:sarah + test/resource:first#notreallybanned@test/user:sarah +assertions: + assertTrue: + - "test/resource:first#view@test/user:sarah" + assertFalse: + - "test/resource:first#view@test/user:tom" diff --git a/internal/services/v1/permissions.go b/internal/services/v1/permissions.go index 6ade020b5f..ea53293efe 100644 --- a/internal/services/v1/permissions.go +++ b/internal/services/v1/permissions.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/authzed/spicedb/pkg/tuple" + "github.com/authzed/authzed-go/pkg/requestmeta" "github.com/authzed/authzed-go/pkg/responsemeta" @@ -368,6 +370,84 @@ func (ps *permissionServer) LookupResources(req *v1.LookupResourcesRequest, resp return nil } +func (ps *permissionServer) LookupSubjects(req *v1.LookupSubjectsRequest, resp v1.PermissionsService_LookupSubjectsServer) error { + ctx := resp.Context() + atRevision, revisionReadAt := consistency.MustRevisionFromContext(ctx) + + ds := datastoremw.MustFromContext(ctx).SnapshotReader(atRevision) + + // Perform our preflight checks in parallel + errG, checksCtx := errgroup.WithContext(ctx) + errG.Go(func() error { + return namespace.CheckNamespaceAndRelation( + checksCtx, + req.Resource.ObjectType, + req.Permission, + false, + ds, + ) + }) + errG.Go(func() error { + return namespace.CheckNamespaceAndRelation( + ctx, + req.SubjectObjectType, + stringz.DefaultEmpty(req.OptionalSubjectRelation, tuple.Ellipsis), + true, + ds, + ) + }) + if err := errG.Wait(); err != nil { + return rewritePermissionsError(ctx, err) + } + + respMetadata := &dispatch.ResponseMeta{ + DispatchCount: 0, + CachedDispatchCount: 0, + DepthRequired: 0, + DebugInfo: nil, + } + usagemetrics.SetInContext(ctx, respMetadata) + + stream := dispatchpkg.NewHandlingDispatchStream(ctx, func(result *dispatch.DispatchLookupSubjectsResponse) error { + for _, foundSubject := range result.FoundSubjects { + err := resp.Send(&v1.LookupSubjectsResponse{ + SubjectObjectId: foundSubject.SubjectId, + ExcludedSubjectIds: foundSubject.ExcludedSubjectIds, + LookedUpAt: revisionReadAt, + }) + if err != nil { + return err + } + } + + dispatchpkg.AddResponseMetadata(respMetadata, result.Metadata) + return nil + }) + + err := ps.dispatch.DispatchLookupSubjects( + &dispatch.DispatchLookupSubjectsRequest{ + Metadata: &dispatch.ResolverMeta{ + AtRevision: atRevision.String(), + DepthRemaining: ps.defaultDepth, + }, + ResourceRelation: &core.RelationReference{ + Namespace: req.Resource.ObjectType, + Relation: req.Permission, + }, + ResourceIds: []string{req.Resource.ObjectId}, + SubjectRelation: &core.RelationReference{ + Namespace: req.SubjectObjectType, + Relation: stringz.DefaultEmpty(req.OptionalSubjectRelation, tuple.Ellipsis), + }, + }, + stream) + if err != nil { + return rewritePermissionsError(ctx, err) + } + + return nil +} + func normalizeSubjectRelation(sub *v1.SubjectReference) string { if sub.OptionalRelation == "" { return graph.Ellipsis diff --git a/internal/services/v1/permissions_test.go b/internal/services/v1/permissions_test.go index 5276a1a861..31acc8cdef 100644 --- a/internal/services/v1/permissions_test.go +++ b/internal/services/v1/permissions_test.go @@ -665,3 +665,152 @@ func TestTranslateExpansionTree(t *testing.T) { }) } } + +func TestLookupSubjects(t *testing.T) { + testCases := []struct { + resource *v1.ObjectReference + permission string + subjectType string + subjectRelation string + + expectedSubjectIds []string + expectedErrorCode codes.Code + }{ + { + obj("document", "companyplan"), + "view", + "user", + "", + []string{"auditor", "legal", "owner"}, + codes.OK, + }, + { + obj("document", "healthplan"), + "view", + "user", + "", + []string{"chief_financial_officer"}, + codes.OK, + }, + { + obj("document", "masterplan"), + "view", + "user", + "", + []string{"auditor", "chief_financial_officer", "eng_lead", "legal", "owner", "product_manager", "vp_product"}, + codes.OK, + }, + { + obj("document", "masterplan"), + "view_and_edit", + "user", + "", + nil, + codes.OK, + }, + { + obj("document", "specialplan"), + "view_and_edit", + "user", + "", + []string{"multiroleguy"}, + codes.OK, + }, + { + obj("document", "unknownobj"), + "view", + "user", + "", + nil, + codes.OK, + }, + { + obj("document", "masterplan"), + "invalidperm", + "user", + "", + nil, + codes.FailedPrecondition, + }, + { + obj("document", "masterplan"), + "view", + "invalidsubtype", + "", + nil, + codes.FailedPrecondition, + }, + { + obj("unknown", "masterplan"), + "view", + "user", + "", + nil, + codes.FailedPrecondition, + }, + { + obj("document", "masterplan"), + "view", + "user", + "invalidrel", + nil, + codes.FailedPrecondition, + }, + } + + for _, delta := range testTimedeltas { + t.Run(fmt.Sprintf("fuzz%d", delta/time.Millisecond), func(t *testing.T) { + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s:%s#%s for %s#%s", tc.resource.ObjectType, tc.resource.ObjectId, tc.permission, tc.subjectType, tc.subjectRelation), func(t *testing.T) { + require := require.New(t) + conn, cleanup, _, revision := testserver.NewTestServer(require, delta, memdb.DisableGC, true, tf.StandardDatastoreWithData) + client := v1.NewPermissionsServiceClient(conn) + t.Cleanup(func() { + goleak.VerifyNone(t, goleak.IgnoreCurrent()) + }) + t.Cleanup(cleanup) + + var trailer metadata.MD + lookupClient, err := client.LookupSubjects(context.Background(), &v1.LookupSubjectsRequest{ + Resource: tc.resource, + Permission: tc.permission, + SubjectObjectType: tc.subjectType, + OptionalSubjectRelation: tc.subjectRelation, + Consistency: &v1.Consistency{ + Requirement: &v1.Consistency_AtLeastAsFresh{ + AtLeastAsFresh: zedtoken.NewFromRevision(revision), + }, + }, + }, grpc.Trailer(&trailer)) + + require.NoError(err) + if tc.expectedErrorCode == codes.OK { + var resolvedObjectIds []string + for { + resp, err := lookupClient.Recv() + if errors.Is(err, io.EOF) { + break + } + + require.NoError(err) + + resolvedObjectIds = append(resolvedObjectIds, resp.SubjectObjectId) + } + + sort.Strings(tc.expectedSubjectIds) + sort.Strings(resolvedObjectIds) + + require.Equal(tc.expectedSubjectIds, resolvedObjectIds) + + dispatchCount, err := responsemeta.GetIntResponseTrailerMetadata(trailer, responsemeta.DispatchedOperationsCount) + require.NoError(err) + require.GreaterOrEqual(dispatchCount, 0) + } else { + _, err := lookupClient.Recv() + grpcutil.RequireStatus(t, tc.expectedErrorCode, err) + } + }) + } + }) + } +} diff --git a/internal/util/basesubjectset.go b/internal/util/basesubjectset.go new file mode 100644 index 0000000000..b47bb47903 --- /dev/null +++ b/internal/util/basesubjectset.go @@ -0,0 +1,255 @@ +package util + +import ( + "golang.org/x/exp/maps" + + "github.com/authzed/spicedb/pkg/tuple" +) + +// Subject is a subject that can be placed into a BaseSubjectSet. +type Subject interface { + // GetSubjectId returns the ID of the subject. For wildcards, this should be `*`. + GetSubjectId() string + + // GetExcludedSubjectIds returns the list of subject IDs excluded. Should only have values + // for wildcards. + GetExcludedSubjectIds() []string +} + +// BaseSubjectSet defines a set that tracks accessible subjects. It is generic to allow +// other implementations to define the kind of tracking information associated with each subject. +// +// NOTE: Unlike a traditional set, unions between wildcards and a concrete subject will result +// in *both* being present in the set, to maintain the proper set semantics around wildcards. +type BaseSubjectSet[T Subject] struct { + values map[string]T + constructor func(subjectID string, excludedSubjectIDs []string, sources ...T) T + combiner func(existing T, added T) T +} + +// NewBaseSubjectSet creates a new base subject set for use underneath well-typed implementation. +// +// The constructor function is a function that returns a new instancre of type T for a particular +// subject ID. +// The combiner function is optional, and if given, is used to combine existing elements in the +// set into a new element. This is typically used in debug packages for tracking of additional +// metadata. +func NewBaseSubjectSet[T Subject]( + constructor func(subjectID string, excludedSubjectIDs []string, sources ...T) T, + combiner func(existing T, added T) T, +) BaseSubjectSet[T] { + return BaseSubjectSet[T]{ + values: map[string]T{}, + constructor: constructor, + combiner: combiner, + } +} + +// Add adds the found subject to the set. This is equivalent to a Union operation between the +// existing set of subjects and a set containing the single subject. +func (bss BaseSubjectSet[T]) Add(foundSubject T) bool { + existing, ok := bss.values[foundSubject.GetSubjectId()] + if !ok { + bss.values[foundSubject.GetSubjectId()] = foundSubject + } + + if foundSubject.GetSubjectId() == tuple.PublicWildcard { + if ok { + // Intersect any exceptions, as union between one wildcard and another is a wildcard + // with the exceptions intersected. + // + // As a concrete example, given `user:* - user:tom` and `user:* - user:sarah`, the union + // of the two will be `*`, since each handles the other user. + excludedIds := NewSet[string](existing.GetExcludedSubjectIds()...).IntersectionDifference(NewSet[string](foundSubject.GetExcludedSubjectIds()...)) + bss.values[tuple.PublicWildcard] = bss.constructor(tuple.PublicWildcard, excludedIds.AsSlice(), existing, foundSubject) + } + } else { + // If there is an existing wildcard, remove the subject from its exclusions list. + if existingWildcard, ok := bss.values[tuple.PublicWildcard]; ok { + excludedIds := NewSet[string](existingWildcard.GetExcludedSubjectIds()...) + excludedIds.Remove(foundSubject.GetSubjectId()) + bss.values[tuple.PublicWildcard] = bss.constructor(tuple.PublicWildcard, excludedIds.AsSlice(), existingWildcard) + } + } + + if bss.combiner != nil && ok { + bss.values[foundSubject.GetSubjectId()] = bss.combiner(bss.values[foundSubject.GetSubjectId()], foundSubject) + } + + return !ok +} + +// Subtract subtracts the given subject found the set. +func (bss BaseSubjectSet[T]) Subtract(foundSubject T) { + // If the subject being removed is a wildcard, then remove any non-excluded items and adjust + // the existing wildcard. + if foundSubject.GetSubjectId() == tuple.PublicWildcard { + exclusions := NewSet[string](foundSubject.GetExcludedSubjectIds()...) + for existingSubjectID := range bss.values { + if existingSubjectID == tuple.PublicWildcard { + continue + } + + if !exclusions.Has(existingSubjectID) { + delete(bss.values, existingSubjectID) + } + } + + // Check for an existing wildcard and adjust accordingly. + if existing, ok := bss.values[tuple.PublicWildcard]; ok { + // A subtraction of a wildcard from another wildcard subtracts the exclusions from the second. + // from the first, and places them into the subject set directly. + // + // As a concrete example, given `user:* - user:tom` - `user:* - user:sarah`, the subtraction + // of the two will be `user:sarah`, since sarah is in the first set and not in the second. + existingExclusions := NewSet[string](existing.GetExcludedSubjectIds()...) + for _, subjectID := range foundSubject.GetExcludedSubjectIds() { + if !existingExclusions.Has(subjectID) { + bss.values[subjectID] = bss.constructor(subjectID, nil, foundSubject) + } + } + } + + delete(bss.values, tuple.PublicWildcard) + return + } + + // Remove the subject itself from the set. + delete(bss.values, foundSubject.GetSubjectId()) + + // If wildcard exists within the subject set, add the found subject to the exclusion list. + if wildcard, ok := bss.values[tuple.PublicWildcard]; ok { + exclusions := NewSet[string](wildcard.GetExcludedSubjectIds()...) + exclusions.Add(foundSubject.GetSubjectId()) + bss.values[tuple.PublicWildcard] = bss.constructor(tuple.PublicWildcard, exclusions.AsSlice(), wildcard) + } +} + +// SubtractAll subtracts the other set of subjects from this set of subtracts, modifying this +// set in place. +func (bss BaseSubjectSet[T]) SubtractAll(other BaseSubjectSet[T]) { + for _, fs := range other.values { + bss.Subtract(fs) + } +} + +// IntersectionDifference performs an intersection between this set and the other set, modifying +// this set in place. +func (bss BaseSubjectSet[T]) IntersectionDifference(other BaseSubjectSet[T]) { + // Check if the other set has a wildcard. If so, remove any subjects found in the exclusion + // list. + // + // As a concrete example, given `user:tom` and `user:* - user:sarah`, the intersection should + // return `user:tom`, because everyone but `sarah` (including `tom`) is in the second set. + otherWildcard, hasOtherWildcard := other.values[tuple.PublicWildcard] + if hasOtherWildcard { + exclusion := NewSet[string](otherWildcard.GetExcludedSubjectIds()...) + for subjectID := range bss.values { + if subjectID != tuple.PublicWildcard { + if exclusion.Has(subjectID) { + delete(bss.values, subjectID) + } + } + } + } + + // Remove any concrete subjects, if the other does not have a wildcard. + if !hasOtherWildcard { + for subjectID := range bss.values { + if subjectID != tuple.PublicWildcard { + if _, ok := other.values[subjectID]; !ok { + delete(bss.values, subjectID) + } + } + } + } + + // Handle the case where the current set has a wildcard. We have to do two operations: + // + // 1) If the current set has a wildcard, either add the exclusions together if the other set + // also has a wildcard, or remove it if it did not. + // + // 2) We also add in any other set members that are not in the wildcard's exclusion set, as + // an intersection between a wildcard with exclusions and concrete types will always return + // concrete types as well. + if wildcard, ok := bss.values[tuple.PublicWildcard]; ok { + exclusions := NewSet[string](wildcard.GetExcludedSubjectIds()...) + + if hasOtherWildcard { + toBeExcluded := NewSet[string]() + toBeExcluded.Extend(wildcard.GetExcludedSubjectIds()) + toBeExcluded.Extend(otherWildcard.GetExcludedSubjectIds()) + bss.values[tuple.PublicWildcard] = bss.constructor(tuple.PublicWildcard, toBeExcluded.AsSlice(), wildcard, otherWildcard) + } else { + // Remove this wildcard. + delete(bss.values, tuple.PublicWildcard) + } + + // Add any concrete items from the other set into this set. This is necebssary because an + // intersection between a wildcard and a concrete should always return that concrete, except + // if it is within the wildcard's exclusion list. + // + // As a concrete example, given `user:* - user:tom` and `user:sarah`, the first set contains + // all users except `tom` (and thus includes `sarah`) and the second is `sarah`, so the result + // must include `sarah`. + for subjectID, fs := range other.values { + if subjectID != tuple.PublicWildcard && !exclusions.Has(subjectID) { + bss.values[subjectID] = fs + } + } + } + + // If a combiner is defined, run it over all values from both sets. + if bss.combiner != nil { + for subjectID := range bss.values { + if added, ok := other.values[subjectID]; ok { + bss.values[subjectID] = bss.combiner(bss.values[subjectID], added) + } + } + } +} + +// UnionWith adds the given subjects to this set, via a union call. +func (bss BaseSubjectSet[T]) UnionWith(foundSubjects []T) { + for _, fs := range foundSubjects { + bss.Add(fs) + } +} + +// UnionWithSet performs a union operation between this set and the other set, modifying this +// set in place. +func (bss BaseSubjectSet[T]) UnionWithSet(other BaseSubjectSet[T]) { + bss.UnionWith(other.AsSlice()) +} + +// Get returns the found subject with the given ID in the set, if any. +func (bss BaseSubjectSet[T]) Get(id string) (T, bool) { + found, ok := bss.values[id] + return found, ok +} + +// IsEmpty returns whether the subject set is empty. +func (bss BaseSubjectSet[T]) IsEmpty() bool { + return len(bss.values) == 0 +} + +// AsSlice returns the contents of the subject set as a slice of found subjects. +func (bss BaseSubjectSet[T]) AsSlice() []T { + slice := make([]T, 0, len(bss.values)) + for _, fs := range bss.values { + slice = append(slice, fs) + } + return slice +} + +// Clone returns a clone of this subject set. Note that this is a shallow clone. +// NOTE: Should only be used when performance is not a concern. +func (bss BaseSubjectSet[T]) Clone() BaseSubjectSet[T] { + return BaseSubjectSet[T]{maps.Clone(bss.values), bss.constructor, bss.combiner} +} + +// UnsafeRemoveExact removes the *exact* matching subject, with no wildcard handling. +// This should ONLY be used for testing. +func (bss BaseSubjectSet[T]) UnsafeRemoveExact(foundSubject T) { + delete(bss.values, foundSubject.GetSubjectId()) +} diff --git a/internal/util/set.go b/internal/util/set.go index 5c33966122..ee421b7d55 100644 --- a/internal/util/set.go +++ b/internal/util/set.go @@ -6,10 +6,14 @@ type Set[T comparable] struct { } // NewSet returns a new set. -func NewSet[T comparable]() *Set[T] { - return &Set[T]{ +func NewSet[T comparable](items ...T) *Set[T] { + s := &Set[T]{ values: map[T]struct{}{}, } + for _, item := range items { + s.values[item] = struct{}{} + } + return s } // Has returns true if the set contains the given value. @@ -48,13 +52,14 @@ func (s *Set[T]) Extend(values []T) { } // IntersectionDifference removes any values from this set that -// are not shared with the other set. -func (s *Set[T]) IntersectionDifference(other *Set[T]) { +// are not shared with the other set. Returns the same set. +func (s *Set[T]) IntersectionDifference(other *Set[T]) *Set[T] { for value := range s.values { if !other.Has(value) { s.Remove(value) } } + return s } // RemoveAll removes all values from this set found in the other set. @@ -64,6 +69,14 @@ func (s *Set[T]) RemoveAll(other *Set[T]) { } } +// Subtract subtracts the other set from this set, returning a new set. +func (s *Set[T]) Subtract(other *Set[T]) *Set[T] { + newSet := NewSet[T]() + newSet.Extend(s.AsSlice()) + newSet.RemoveAll(other) + return newSet +} + // IsEmpty returns true if the set is empty. func (s *Set[T]) IsEmpty() bool { return len(s.values) == 0 @@ -71,6 +84,10 @@ func (s *Set[T]) IsEmpty() bool { // AsSlice returns the set as a slice of values. func (s *Set[T]) AsSlice() []T { + if len(s.values) == 0 { + return nil + } + slice := make([]T, 0, len(s.values)) for value := range s.values { slice = append(slice, value) diff --git a/internal/util/set_test.go b/internal/util/set_test.go index f551a8862f..0472997416 100644 --- a/internal/util/set_test.go +++ b/internal/util/set_test.go @@ -89,7 +89,7 @@ func TestSetIntersectionDifference(t *testing.T) { { []int{1, 3, 5, 7, 9}, []int{2, 4, 6, 8, 10}, - []int{}, + nil, }, { []int{1, 2, 3, 4, 5}, diff --git a/internal/util/subjectset.go b/internal/util/subjectset.go new file mode 100644 index 0000000000..922e23fc00 --- /dev/null +++ b/internal/util/subjectset.go @@ -0,0 +1,41 @@ +package util + +import ( + v1 "github.com/authzed/spicedb/pkg/proto/dispatch/v1" +) + +// SubjectSet defines a set that tracks accessible subjects. +// +// NOTE: Unlike a traditional set, unions between wildcards and a concrete subject will result +// in *both* being present in the set, to maintain the proper set semantics around wildcards. +type SubjectSet struct { + BaseSubjectSet[*v1.FoundSubject] +} + +// NewSubjectSet creates and returns a new subject set. +func NewSubjectSet() SubjectSet { + return SubjectSet{ + BaseSubjectSet: BaseSubjectSet[*v1.FoundSubject]{ + values: map[string]*v1.FoundSubject{}, + constructor: func(subjectID string, excludedSubjectIDs []string, sources ...*v1.FoundSubject) *v1.FoundSubject { + return &v1.FoundSubject{ + SubjectId: subjectID, + ExcludedSubjectIds: excludedSubjectIDs, + } + }, + combiner: nil, + }, + } +} + +func (ss SubjectSet) SubtractAll(other SubjectSet) { + ss.BaseSubjectSet.SubtractAll(other.BaseSubjectSet) +} + +func (ss SubjectSet) IntersectionDifference(other SubjectSet) { + ss.BaseSubjectSet.IntersectionDifference(other.BaseSubjectSet) +} + +func (ss SubjectSet) UnionWithSet(other SubjectSet) { + ss.BaseSubjectSet.UnionWithSet(other.BaseSubjectSet) +} diff --git a/internal/util/subjectset_test.go b/internal/util/subjectset_test.go new file mode 100644 index 0000000000..917eb364f4 --- /dev/null +++ b/internal/util/subjectset_test.go @@ -0,0 +1,374 @@ +package util + +import ( + "sort" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + v1 "github.com/authzed/spicedb/pkg/proto/dispatch/v1" + "github.com/authzed/spicedb/pkg/tuple" +) + +func sub(subjectID string) *v1.FoundSubject { + return &v1.FoundSubject{ + SubjectId: subjectID, + } +} + +func wc(exclusions ...string) *v1.FoundSubject { + return &v1.FoundSubject{ + SubjectId: tuple.PublicWildcard, + ExcludedSubjectIds: exclusions, + } +} + +func TestSubjectSetAdd(t *testing.T) { + tcs := []struct { + name string + existing []*v1.FoundSubject + toAdd *v1.FoundSubject + expectedResult bool + expectedSet []*v1.FoundSubject + }{ + { + "basic add", + []*v1.FoundSubject{sub("foo"), sub("bar")}, + sub("baz"), + true, + []*v1.FoundSubject{sub("foo"), sub("bar"), sub("baz")}, + }, + { + "basic repeated add", + []*v1.FoundSubject{sub("foo"), sub("bar")}, + sub("bar"), + false, + []*v1.FoundSubject{sub("foo"), sub("bar")}, + }, + { + "add of an empty wildcard", + []*v1.FoundSubject{sub("foo"), sub("bar")}, + wc(), + true, + []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, + }, + { + "add of a wildcard with exclusions", + []*v1.FoundSubject{sub("foo"), sub("bar")}, + wc("1", "2"), + true, + []*v1.FoundSubject{sub("foo"), sub("bar"), wc("1", "2")}, + }, + { + "add of a wildcard to a wildcard", + []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, + wc(), + false, + []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, + }, + { + "add of a wildcard with exclusions to a bare wildcard", + []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, + wc("1", "2"), + false, + []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, + }, + { + "add of a bare wildcard to a wildcard with exclusions", + []*v1.FoundSubject{sub("foo"), sub("bar"), wc("1", "2")}, + wc(), + false, + []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, + }, + { + "add of a wildcard with exclusions to a wildcard with exclusions", + []*v1.FoundSubject{sub("foo"), sub("bar"), wc("1", "2")}, + wc("2", "3"), + false, + []*v1.FoundSubject{sub("foo"), sub("bar"), wc("2")}, + }, + { + "add of a subject to a wildcard with exclusions that does not have that subject", + []*v1.FoundSubject{wc("1", "2")}, + sub("3"), + true, + []*v1.FoundSubject{wc("1", "2"), sub("3")}, + }, + { + "add of a subject to a wildcard with exclusions that has that subject", + []*v1.FoundSubject{wc("1", "2")}, + sub("2"), + true, + []*v1.FoundSubject{wc("1"), sub("2")}, + }, + { + "add of a subject to a bare wildcard", + []*v1.FoundSubject{wc()}, + sub("1"), + true, + []*v1.FoundSubject{wc(), sub("1")}, + }, + { + "add of two wildcards", + []*v1.FoundSubject{wc("1")}, + wc("2"), + false, + []*v1.FoundSubject{wc()}, + }, + { + "add of two wildcards with same restrictions", + []*v1.FoundSubject{wc("1")}, + wc("1", "2"), + false, + []*v1.FoundSubject{wc("1")}, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + existingSet := NewSubjectSet() + for _, existing := range tc.existing { + require.True(t, existingSet.Add(existing)) + } + + require.Equal(t, tc.expectedResult, existingSet.Add(tc.toAdd)) + + expectedSet := tc.expectedSet + computedSet := existingSet.AsSlice() + + sort.Sort(sortByID(expectedSet)) + sort.Sort(sortByID(computedSet)) + + stableSortExclusions(expectedSet) + stableSortExclusions(computedSet) + + require.Equal(t, expectedSet, computedSet) + }) + } +} + +func TestSubjectSetSubtract(t *testing.T) { + tcs := []struct { + name string + existing []*v1.FoundSubject + toSubtract *v1.FoundSubject + expectedSet []*v1.FoundSubject + }{ + { + "basic subtract, no overlap", + []*v1.FoundSubject{sub("foo"), sub("bar")}, + sub("baz"), + []*v1.FoundSubject{sub("foo"), sub("bar")}, + }, + { + "basic subtract, with overlap", + []*v1.FoundSubject{sub("foo"), sub("bar")}, + sub("bar"), + []*v1.FoundSubject{sub("foo")}, + }, + { + "basic subtract from bare wildcard", + []*v1.FoundSubject{sub("foo"), wc()}, + sub("bar"), + []*v1.FoundSubject{sub("foo"), wc("bar")}, + }, + { + "subtract from bare wildcard and set", + []*v1.FoundSubject{sub("bar"), wc()}, + sub("bar"), + []*v1.FoundSubject{wc("bar")}, + }, + { + "subtract from wildcard", + []*v1.FoundSubject{sub("bar"), wc("bar")}, + sub("bar"), + []*v1.FoundSubject{wc("bar")}, + }, + { + "subtract from wildcard with existing exclusions", + []*v1.FoundSubject{sub("bar"), wc("hiya")}, + sub("bar"), + []*v1.FoundSubject{wc("bar", "hiya")}, + }, + { + "subtract bare wildcard from set", + []*v1.FoundSubject{sub("bar"), sub("foo")}, + wc(), + []*v1.FoundSubject{}, + }, + { + "subtract wildcard with no matching exclusions from set", + []*v1.FoundSubject{sub("bar"), sub("foo")}, + wc("baz"), + []*v1.FoundSubject{}, + }, + { + "subtract wildcard with matching exclusions from set", + []*v1.FoundSubject{sub("bar"), sub("foo")}, + wc("bar"), + []*v1.FoundSubject{sub("bar")}, + }, + { + "subtract wildcard from another wildcard, both with the same exclusions", + []*v1.FoundSubject{wc("sarah")}, + wc("sarah"), + []*v1.FoundSubject{}, + }, + { + "subtract wildcard from another wildcard, with different exclusions", + []*v1.FoundSubject{wc("tom"), sub("foo"), sub("bar")}, + wc("sarah"), + []*v1.FoundSubject{sub("sarah")}, + }, + { + "subtract wildcard from another wildcard, with more exclusions", + []*v1.FoundSubject{wc("sarah")}, + wc("sarah", "tom"), + []*v1.FoundSubject{sub("tom")}, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + existingSet := NewSubjectSet() + for _, existing := range tc.existing { + require.True(t, existingSet.Add(existing)) + } + + existingSet.Subtract(tc.toSubtract) + + expectedSet := tc.expectedSet + computedSet := existingSet.AsSlice() + + sort.Sort(sortByID(expectedSet)) + sort.Sort(sortByID(computedSet)) + + stableSortExclusions(expectedSet) + stableSortExclusions(computedSet) + + require.Equal(t, expectedSet, computedSet) + }) + } +} + +func TestSubjectSetIntersection(t *testing.T) { + tcs := []struct { + name string + existing []*v1.FoundSubject + toIntersect []*v1.FoundSubject + expectedSet []*v1.FoundSubject + }{ + { + "basic intersection, full overlap", + []*v1.FoundSubject{sub("foo"), sub("bar")}, + []*v1.FoundSubject{sub("foo"), sub("bar")}, + []*v1.FoundSubject{sub("foo"), sub("bar")}, + }, + { + "basic intersection, partial overlap", + []*v1.FoundSubject{sub("foo"), sub("bar")}, + []*v1.FoundSubject{sub("foo")}, + []*v1.FoundSubject{sub("foo")}, + }, + { + "basic intersection, no overlap", + []*v1.FoundSubject{sub("foo"), sub("bar")}, + []*v1.FoundSubject{sub("baz")}, + []*v1.FoundSubject{}, + }, + { + "intersection between bare wildcard and concrete", + []*v1.FoundSubject{sub("foo")}, + []*v1.FoundSubject{wc()}, + []*v1.FoundSubject{sub("foo")}, + }, + { + "intersection between wildcard with exclusions and concrete", + []*v1.FoundSubject{sub("foo")}, + []*v1.FoundSubject{wc("tom")}, + []*v1.FoundSubject{sub("foo")}, + }, + { + "intersection between wildcard with matching exclusions and concrete", + []*v1.FoundSubject{sub("foo")}, + []*v1.FoundSubject{wc("foo")}, + []*v1.FoundSubject{}, + }, + { + "intersection between bare wildcards", + []*v1.FoundSubject{wc()}, + []*v1.FoundSubject{wc()}, + []*v1.FoundSubject{wc()}, + }, + { + "intersection between bare wildcard and one with exclusions", + []*v1.FoundSubject{wc()}, + []*v1.FoundSubject{wc("1", "2")}, + []*v1.FoundSubject{wc("1", "2")}, + }, + { + "intersection between wildcards", + []*v1.FoundSubject{wc("2", "3")}, + []*v1.FoundSubject{wc("1", "2")}, + []*v1.FoundSubject{wc("1", "2", "3")}, + }, + { + "intersection wildcard with exclusions and concrete", + []*v1.FoundSubject{wc("2", "3")}, + []*v1.FoundSubject{sub("4")}, + []*v1.FoundSubject{sub("4")}, + }, + { + "intersection wildcard with matching exclusions and concrete", + []*v1.FoundSubject{wc("2", "3", "4")}, + []*v1.FoundSubject{sub("4")}, + []*v1.FoundSubject{}, + }, + { + "intersection of wildcards and two concrete types", + []*v1.FoundSubject{wc(), sub("1")}, + []*v1.FoundSubject{wc(), sub("2")}, + []*v1.FoundSubject{wc(), sub("1"), sub("2")}, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + existingSet := NewSubjectSet() + for _, existing := range tc.existing { + require.True(t, existingSet.Add(existing)) + } + + toIntersect := NewSubjectSet() + for _, toAdd := range tc.toIntersect { + require.True(t, toIntersect.Add(toAdd)) + } + + existingSet.IntersectionDifference(toIntersect) + + expectedSet := tc.expectedSet + computedSet := existingSet.AsSlice() + + sort.Sort(sortByID(expectedSet)) + sort.Sort(sortByID(computedSet)) + + stableSortExclusions(expectedSet) + stableSortExclusions(computedSet) + + require.Equal(t, expectedSet, computedSet) + }) + } +} + +type sortByID []*v1.FoundSubject + +func (a sortByID) Len() int { return len(a) } +func (a sortByID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a sortByID) Less(i, j int) bool { return strings.Compare(a[i].SubjectId, a[j].SubjectId) < 0 } + +func stableSortExclusions(fss []*v1.FoundSubject) { + for _, fs := range fss { + sort.Strings(fs.ExcludedSubjectIds) + } +} diff --git a/pkg/proto/core/v1/core.pb.go b/pkg/proto/core/v1/core.pb.go index 1f1fd731b9..f4d5822bca 100644 --- a/pkg/proto/core/v1/core.pb.go +++ b/pkg/proto/core/v1/core.pb.go @@ -129,15 +129,12 @@ func (SetOperationUserset_Operation) EnumDescriptor() ([]byte, []int) { type ReachabilityEntrypoint_ReachabilityEntrypointKind int32 const ( - // // RELATION_ENTRYPOINT indicates an entrypoint where the subject object can be directly // found for a relationship. ReachabilityEntrypoint_RELATION_ENTRYPOINT ReachabilityEntrypoint_ReachabilityEntrypointKind = 0 - // // COMPUTED_USERSET_ENTRYPOINT indicates an entrypoint where the subject's relation is // "rewritten" via a `computed_userset` to the target permission's operation node. ReachabilityEntrypoint_COMPUTED_USERSET_ENTRYPOINT ReachabilityEntrypoint_ReachabilityEntrypointKind = 1 - // // TUPLESET_TO_USERSET_ENTRYPOINT indicates an entrypoint where the subject's relation is // walked via a `tupleset_to_userset` in the target permission's operation node. ReachabilityEntrypoint_TUPLESET_TO_USERSET_ENTRYPOINT ReachabilityEntrypoint_ReachabilityEntrypointKind = 2 @@ -187,12 +184,10 @@ func (ReachabilityEntrypoint_ReachabilityEntrypointKind) EnumDescriptor() ([]byt type ReachabilityEntrypoint_EntrypointResultStatus int32 const ( - // // REACHABLE_CONDITIONAL_RESULT indicates that the entrypoint is under one or more intersections // or exclusion operations, indicating that any reachable object *may* be a result, conditional // on the parent non-union operation(s). ReachabilityEntrypoint_REACHABLE_CONDITIONAL_RESULT ReachabilityEntrypoint_EntrypointResultStatus = 0 - // // DIRECT_OPERATION_RESULT indicates that the entrypoint exists solely under zero or more // union operations, making any reachable object also a *result* of the relation or permission. ReachabilityEntrypoint_DIRECT_OPERATION_RESULT ReachabilityEntrypoint_EntrypointResultStatus = 1 @@ -571,6 +566,7 @@ type RelationTupleTreeNode struct { unknownFields protoimpl.UnknownFields // Types that are assignable to NodeType: + // // *RelationTupleTreeNode_IntermediateNode // *RelationTupleTreeNode_LeafNode NodeType isRelationTupleTreeNode_NodeType `protobuf_oneof:"node_type"` @@ -755,7 +751,6 @@ func (x *DirectSubjects) GetSubjects() []*ObjectAndRelation { return nil } -// // Metadata is compiler metadata added to namespace definitions, such as doc comments and // relation kinds. type Metadata struct { @@ -805,7 +800,6 @@ func (x *Metadata) GetMetadataMessage() []*anypb.Any { return nil } -// // NamespaceDefinition represents a single definition of an object type type NamespaceDefinition struct { state protoimpl.MessageState @@ -882,7 +876,6 @@ func (x *NamespaceDefinition) GetSourcePosition() *SourcePosition { return nil } -// // Relation represents the definition of a relation or permission under a namespace. type Relation struct { state protoimpl.MessageState @@ -893,7 +886,6 @@ type Relation struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // userset_rewrite, if specified, is the rewrite for computing the value of the permission. UsersetRewrite *UsersetRewrite `protobuf:"bytes,2,opt,name=userset_rewrite,json=usersetRewrite,proto3" json:"userset_rewrite,omitempty"` - // // type_information, if specified, is the list of allowed object types that can appear in this // relation TypeInformation *TypeInformation `protobuf:"bytes,3,opt,name=type_information,json=typeInformation,proto3" json:"type_information,omitempty"` @@ -986,7 +978,6 @@ func (x *Relation) GetCanonicalCacheKey() string { return "" } -// // ReachabilityGraph is a serialized form of a reachability graph, representing how a relation can // be reached from one or more subject types. // @@ -995,37 +986,37 @@ func (x *Relation) GetCanonicalCacheKey() string { // // For example, given the schema: // ``` -// definition user {} // -// definition organization { -// relation admin: user -// } +// definition user {} +// +// definition organization { +// relation admin: user +// } +// +// definition resource { +// relation org: organization +// relation viewer: user +// relation owner: user +// permission view = viewer + owner + org->admin +// } // -// definition resource { -// relation org: organization -// relation viewer: user -// relation owner: user -// permission view = viewer + owner + org->admin -// } // ``` // // The reachability graph for `viewer` and the other relations will have entrypoints for each // subject type found for those relations. // // The full reachability graph for the `view` relation will have three entrypoints, representing: -// 1) resource#viewer (computed_userset) -// 2) resource#owner (computed_userset) -// 3) organization#admin (tupleset_to_userset) +// 1. resource#viewer (computed_userset) +// 2. resource#owner (computed_userset) +// 3. organization#admin (tupleset_to_userset) type ReachabilityGraph struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // // entrypoints_by_subject_type provides all entrypoints by subject *type*, representing wildcards. // The keys of the map are the full path(s) for the namespace(s) referenced by reachable wildcards EntrypointsBySubjectType map[string]*ReachabilityEntrypoints `protobuf:"bytes,1,rep,name=entrypoints_by_subject_type,json=entrypointsBySubjectType,proto3" json:"entrypoints_by_subject_type,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - // // entrypoints_by_subject_relation provides all entrypoints by subject type+relation. // The keys of the map are of the form `namespace_path#relation_name` EntrypointsBySubjectRelation map[string]*ReachabilityEntrypoints `protobuf:"bytes,2,rep,name=entrypoints_by_subject_relation,json=entrypointsBySubjectRelation,proto3" json:"entrypoints_by_subject_relation,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` @@ -1077,7 +1068,6 @@ func (x *ReachabilityGraph) GetEntrypointsBySubjectRelation() map[string]*Reacha return nil } -// // ReachabilityEntrypoints represents all the entrypoints for a specific subject type or subject // relation into the reachability graph for a particular target relation. type ReachabilityEntrypoints struct { @@ -1085,14 +1075,11 @@ type ReachabilityEntrypoints struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // // entrypoints are the entrypoints found. Entrypoints []*ReachabilityEntrypoint `protobuf:"bytes,1,rep,name=entrypoints,proto3" json:"entrypoints,omitempty"` - // // subject_type, if specified, is the type of subjects to which the entrypoint(s) apply. A // subject type is only set for wildcards. SubjectType string `protobuf:"bytes,2,opt,name=subject_type,json=subjectType,proto3" json:"subject_type,omitempty"` - // // subject_relation, if specified, is the type and relation of subjects to which the // entrypoint(s) apply. SubjectRelation *RelationReference `protobuf:"bytes,3,opt,name=subject_relation,json=subjectRelation,proto3" json:"subject_relation,omitempty"` @@ -1151,7 +1138,6 @@ func (x *ReachabilityEntrypoints) GetSubjectRelation() *RelationReference { return nil } -// // ReachabilityEntrypoint represents a single entrypoint for a specific subject type or subject // relation into the reachability graph for a particular target relation. type ReachabilityEntrypoint struct { @@ -1159,17 +1145,13 @@ type ReachabilityEntrypoint struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // // kind is the kind of the entrypoint. Kind ReachabilityEntrypoint_ReachabilityEntrypointKind `protobuf:"varint,1,opt,name=kind,proto3,enum=core.v1.ReachabilityEntrypoint_ReachabilityEntrypointKind" json:"kind,omitempty"` - // // target_relation is the relation on which the entrypoint exists. TargetRelation *RelationReference `protobuf:"bytes,2,opt,name=target_relation,json=targetRelation,proto3" json:"target_relation,omitempty"` - // // result_status contains the status of objects found for this entrypoint as direct results for // the parent relation/permission. ResultStatus ReachabilityEntrypoint_EntrypointResultStatus `protobuf:"varint,4,opt,name=result_status,json=resultStatus,proto3,enum=core.v1.ReachabilityEntrypoint_EntrypointResultStatus" json:"result_status,omitempty"` - // // tupleset_relation is the name of the tupleset relation on the TupleToUserset this entrypoint // represents, if applicable. TuplesetRelation string `protobuf:"bytes,5,opt,name=tupleset_relation,json=tuplesetRelation,proto3" json:"tupleset_relation,omitempty"` @@ -1235,14 +1217,12 @@ func (x *ReachabilityEntrypoint) GetTuplesetRelation() string { return "" } -// // TypeInformation defines the allowed types for a relation. type TypeInformation struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // // allowed_direct_relations are those relation types allowed to be placed into a relation, // e.g. the types of subjects allowed when a relationship is written to the relation AllowedDirectRelations []*AllowedRelation `protobuf:"bytes,1,rep,name=allowed_direct_relations,json=allowedDirectRelations,proto3" json:"allowed_direct_relations,omitempty"` @@ -1287,7 +1267,6 @@ func (x *TypeInformation) GetAllowedDirectRelations() []*AllowedRelation { return nil } -// // AllowedRelation is an allowed type of a relation when used as a subject. type AllowedRelation struct { state protoimpl.MessageState @@ -1296,10 +1275,10 @@ type AllowedRelation struct { // namespace is the full namespace path of the allowed object type Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"` - // // relation_or_wildcard indicates the relation for the objects, or a wildcard. // // Types that are assignable to RelationOrWildcard: + // // *AllowedRelation_Relation // *AllowedRelation_PublicWildcard_ RelationOrWildcard isAllowedRelation_RelationOrWildcard `protobuf_oneof:"relation_or_wildcard"` @@ -1396,6 +1375,7 @@ type UsersetRewrite struct { unknownFields protoimpl.UnknownFields // Types that are assignable to RewriteOperation: + // // *UsersetRewrite_Union // *UsersetRewrite_Intersection // *UsersetRewrite_Exclusion @@ -1764,6 +1744,7 @@ type SetOperation_Child struct { unknownFields protoimpl.UnknownFields // Types that are assignable to ChildType: + // // *SetOperation_Child_XThis // *SetOperation_Child_ComputedUserset // *SetOperation_Child_TupleToUserset @@ -1771,7 +1752,6 @@ type SetOperation_Child struct { // *SetOperation_Child_XNil ChildType isSetOperation_Child_ChildType `protobuf_oneof:"child_type"` SourcePosition *SourcePosition `protobuf:"bytes,5,opt,name=source_position,json=sourcePosition,proto3" json:"source_position,omitempty"` - // // operation_path (if specified) is the *unique* ID for the set operation in the permission // definition. It is a heirarchy representing the position of the operation under its parent // operation. For example, the operation path of an operation which is the third child of the diff --git a/pkg/proto/dispatch/v1/dispatch.pb.go b/pkg/proto/dispatch/v1/dispatch.pb.go index 042b91558f..981f8249f4 100644 --- a/pkg/proto/dispatch/v1/dispatch.pb.go +++ b/pkg/proto/dispatch/v1/dispatch.pb.go @@ -166,11 +166,9 @@ func (DispatchExpandRequest_ExpansionMode) EnumDescriptor() ([]byte, []int) { type ReachableResource_ResultStatus int32 const ( - // // REQUIRES_CHECK indicates that the resource is reachable but a Check is required to // determine if the resource is actually found for the user. ReachableResource_REQUIRES_CHECK ReachableResource_ResultStatus = 0 - // // HAS_PERMISSION indicates that the resource is both reachable and found for the permission // for the subject. ReachableResource_HAS_PERMISSION ReachableResource_ResultStatus = 1 @@ -261,7 +259,7 @@ func (x CheckDebugTrace_RelationType) Number() protoreflect.EnumNumber { // Deprecated: Use CheckDebugTrace_RelationType.Descriptor instead. func (CheckDebugTrace_RelationType) EnumDescriptor() ([]byte, []int) { - return file_dispatch_v1_dispatch_proto_rawDescGZIP(), []int{14, 0} + return file_dispatch_v1_dispatch_proto_rawDescGZIP(), []int{15, 0} } type DispatchCheckRequest struct { @@ -910,19 +908,74 @@ func (x *DispatchLookupSubjectsRequest) GetSubjectRelation() *v1.RelationReferen return nil } +type FoundSubject struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SubjectId string `protobuf:"bytes,1,opt,name=subject_id,json=subjectId,proto3" json:"subject_id,omitempty"` + ExcludedSubjectIds []string `protobuf:"bytes,2,rep,name=excluded_subject_ids,json=excludedSubjectIds,proto3" json:"excluded_subject_ids,omitempty"` +} + +func (x *FoundSubject) Reset() { + *x = FoundSubject{} + if protoimpl.UnsafeEnabled { + mi := &file_dispatch_v1_dispatch_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FoundSubject) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FoundSubject) ProtoMessage() {} + +func (x *FoundSubject) ProtoReflect() protoreflect.Message { + mi := &file_dispatch_v1_dispatch_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FoundSubject.ProtoReflect.Descriptor instead. +func (*FoundSubject) Descriptor() ([]byte, []int) { + return file_dispatch_v1_dispatch_proto_rawDescGZIP(), []int{10} +} + +func (x *FoundSubject) GetSubjectId() string { + if x != nil { + return x.SubjectId + } + return "" +} + +func (x *FoundSubject) GetExcludedSubjectIds() []string { + if x != nil { + return x.ExcludedSubjectIds + } + return nil +} + type DispatchLookupSubjectsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - FoundSubjectIds []string `protobuf:"bytes,1,rep,name=found_subject_ids,json=foundSubjectIds,proto3" json:"found_subject_ids,omitempty"` - Metadata *ResponseMeta `protobuf:"bytes,2,opt,name=metadata,proto3" json:"metadata,omitempty"` + FoundSubjects []*FoundSubject `protobuf:"bytes,1,rep,name=found_subjects,json=foundSubjects,proto3" json:"found_subjects,omitempty"` + Metadata *ResponseMeta `protobuf:"bytes,2,opt,name=metadata,proto3" json:"metadata,omitempty"` } func (x *DispatchLookupSubjectsResponse) Reset() { *x = DispatchLookupSubjectsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_dispatch_v1_dispatch_proto_msgTypes[10] + mi := &file_dispatch_v1_dispatch_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -935,7 +988,7 @@ func (x *DispatchLookupSubjectsResponse) String() string { func (*DispatchLookupSubjectsResponse) ProtoMessage() {} func (x *DispatchLookupSubjectsResponse) ProtoReflect() protoreflect.Message { - mi := &file_dispatch_v1_dispatch_proto_msgTypes[10] + mi := &file_dispatch_v1_dispatch_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -948,12 +1001,12 @@ func (x *DispatchLookupSubjectsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DispatchLookupSubjectsResponse.ProtoReflect.Descriptor instead. func (*DispatchLookupSubjectsResponse) Descriptor() ([]byte, []int) { - return file_dispatch_v1_dispatch_proto_rawDescGZIP(), []int{10} + return file_dispatch_v1_dispatch_proto_rawDescGZIP(), []int{11} } -func (x *DispatchLookupSubjectsResponse) GetFoundSubjectIds() []string { +func (x *DispatchLookupSubjectsResponse) GetFoundSubjects() []*FoundSubject { if x != nil { - return x.FoundSubjectIds + return x.FoundSubjects } return nil } @@ -977,7 +1030,7 @@ type ResolverMeta struct { func (x *ResolverMeta) Reset() { *x = ResolverMeta{} if protoimpl.UnsafeEnabled { - mi := &file_dispatch_v1_dispatch_proto_msgTypes[11] + mi := &file_dispatch_v1_dispatch_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -990,7 +1043,7 @@ func (x *ResolverMeta) String() string { func (*ResolverMeta) ProtoMessage() {} func (x *ResolverMeta) ProtoReflect() protoreflect.Message { - mi := &file_dispatch_v1_dispatch_proto_msgTypes[11] + mi := &file_dispatch_v1_dispatch_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1003,7 +1056,7 @@ func (x *ResolverMeta) ProtoReflect() protoreflect.Message { // Deprecated: Use ResolverMeta.ProtoReflect.Descriptor instead. func (*ResolverMeta) Descriptor() ([]byte, []int) { - return file_dispatch_v1_dispatch_proto_rawDescGZIP(), []int{11} + return file_dispatch_v1_dispatch_proto_rawDescGZIP(), []int{12} } func (x *ResolverMeta) GetAtRevision() string { @@ -1034,7 +1087,7 @@ type ResponseMeta struct { func (x *ResponseMeta) Reset() { *x = ResponseMeta{} if protoimpl.UnsafeEnabled { - mi := &file_dispatch_v1_dispatch_proto_msgTypes[12] + mi := &file_dispatch_v1_dispatch_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1047,7 +1100,7 @@ func (x *ResponseMeta) String() string { func (*ResponseMeta) ProtoMessage() {} func (x *ResponseMeta) ProtoReflect() protoreflect.Message { - mi := &file_dispatch_v1_dispatch_proto_msgTypes[12] + mi := &file_dispatch_v1_dispatch_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1060,7 +1113,7 @@ func (x *ResponseMeta) ProtoReflect() protoreflect.Message { // Deprecated: Use ResponseMeta.ProtoReflect.Descriptor instead. func (*ResponseMeta) Descriptor() ([]byte, []int) { - return file_dispatch_v1_dispatch_proto_rawDescGZIP(), []int{12} + return file_dispatch_v1_dispatch_proto_rawDescGZIP(), []int{13} } func (x *ResponseMeta) GetDispatchCount() uint32 { @@ -1102,7 +1155,7 @@ type DebugInformation struct { func (x *DebugInformation) Reset() { *x = DebugInformation{} if protoimpl.UnsafeEnabled { - mi := &file_dispatch_v1_dispatch_proto_msgTypes[13] + mi := &file_dispatch_v1_dispatch_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1115,7 +1168,7 @@ func (x *DebugInformation) String() string { func (*DebugInformation) ProtoMessage() {} func (x *DebugInformation) ProtoReflect() protoreflect.Message { - mi := &file_dispatch_v1_dispatch_proto_msgTypes[13] + mi := &file_dispatch_v1_dispatch_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1128,7 +1181,7 @@ func (x *DebugInformation) ProtoReflect() protoreflect.Message { // Deprecated: Use DebugInformation.ProtoReflect.Descriptor instead. func (*DebugInformation) Descriptor() ([]byte, []int) { - return file_dispatch_v1_dispatch_proto_rawDescGZIP(), []int{13} + return file_dispatch_v1_dispatch_proto_rawDescGZIP(), []int{14} } func (x *DebugInformation) GetCheck() *CheckDebugTrace { @@ -1153,7 +1206,7 @@ type CheckDebugTrace struct { func (x *CheckDebugTrace) Reset() { *x = CheckDebugTrace{} if protoimpl.UnsafeEnabled { - mi := &file_dispatch_v1_dispatch_proto_msgTypes[14] + mi := &file_dispatch_v1_dispatch_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1166,7 +1219,7 @@ func (x *CheckDebugTrace) String() string { func (*CheckDebugTrace) ProtoMessage() {} func (x *CheckDebugTrace) ProtoReflect() protoreflect.Message { - mi := &file_dispatch_v1_dispatch_proto_msgTypes[14] + mi := &file_dispatch_v1_dispatch_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1179,7 +1232,7 @@ func (x *CheckDebugTrace) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckDebugTrace.ProtoReflect.Descriptor instead. func (*CheckDebugTrace) Descriptor() ([]byte, []int) { - return file_dispatch_v1_dispatch_proto_rawDescGZIP(), []int{14} + return file_dispatch_v1_dispatch_proto_rawDescGZIP(), []int{15} } func (x *CheckDebugTrace) GetRequest() *DispatchCheckRequest { @@ -1385,111 +1438,118 @@ var file_dispatch_v1_dispatch_proto_rawDesc = []byte{ 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x22, 0x83, 0x01, 0x0a, 0x1e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, - 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x5f, - 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x49, - 0x64, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x7d, 0x0a, 0x0c, 0x52, 0x65, 0x73, - 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x3b, 0x0a, 0x0b, 0x61, 0x74, 0x5f, - 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1a, - 0xfa, 0x42, 0x17, 0x72, 0x15, 0x32, 0x13, 0x5e, 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x28, 0x5c, - 0x2e, 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x29, 0x3f, 0x24, 0x52, 0x0a, 0x61, 0x74, 0x52, 0x65, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x0f, 0x64, 0x65, 0x70, 0x74, 0x68, 0x5f, - 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, - 0x07, 0xfa, 0x42, 0x04, 0x2a, 0x02, 0x20, 0x00, 0x52, 0x0e, 0x64, 0x65, 0x70, 0x74, 0x68, 0x52, - 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0xda, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x69, 0x73, - 0x70, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x70, 0x74, 0x68, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, - 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x64, 0x65, 0x70, 0x74, 0x68, 0x52, - 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x61, 0x63, 0x68, 0x65, - 0x64, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x63, 0x61, 0x63, 0x68, 0x65, 0x64, 0x44, 0x69, - 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0a, 0x64, - 0x65, 0x62, 0x75, 0x67, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1d, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, - 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, - 0x64, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, - 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, 0x46, 0x0a, 0x10, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, - 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x63, 0x68, 0x65, - 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, - 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x62, 0x75, - 0x67, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x05, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x22, 0xfc, 0x02, - 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x54, 0x72, 0x61, 0x63, - 0x65, 0x12, 0x3b, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, - 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5f, - 0x0a, 0x16, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, - 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x54, 0x72, 0x61, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x6c, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x25, 0x0a, 0x0e, 0x68, 0x61, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x68, 0x61, 0x73, 0x50, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x73, 0x5f, 0x63, 0x61, 0x63, - 0x68, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0e, 0x69, 0x73, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x12, 0x3f, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x5f, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x73, - 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, - 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x54, - 0x72, 0x61, 0x63, 0x65, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x50, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, - 0x73, 0x22, 0x39, 0x0a, 0x0c, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0c, - 0x0a, 0x08, 0x52, 0x45, 0x4c, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, - 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x32, 0xa0, 0x04, 0x0a, - 0x0f, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x58, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x12, 0x21, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, - 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, - 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0e, 0x44, 0x69, - 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x12, 0x22, 0x2e, 0x64, - 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, - 0x74, 0x63, 0x68, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x23, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, - 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x70, 0x61, - 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x12, 0x22, 0x2e, 0x64, 0x69, 0x73, 0x70, + 0x69, 0x6f, 0x6e, 0x22, 0x5f, 0x0a, 0x0c, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x5f, 0x73, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x12, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x49, 0x64, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x1e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, + 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0e, 0x66, 0x6f, 0x75, 0x6e, 0x64, + 0x5f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, + 0x75, 0x6e, 0x64, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x0d, 0x66, 0x6f, 0x75, 0x6e, + 0x64, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x35, 0x0a, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x64, 0x69, + 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x22, 0x7d, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x61, + 0x12, 0x3b, 0x0a, 0x0b, 0x61, 0x74, 0x5f, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1a, 0xfa, 0x42, 0x17, 0x72, 0x15, 0x32, 0x13, 0x5e, 0x5b, + 0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x28, 0x5c, 0x2e, 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x2b, 0x29, 0x3f, + 0x24, 0x52, 0x0a, 0x61, 0x74, 0x52, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, + 0x0f, 0x64, 0x65, 0x70, 0x74, 0x68, 0x5f, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x2a, 0x02, 0x20, 0x00, 0x52, + 0x0e, 0x64, 0x65, 0x70, 0x74, 0x68, 0x52, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x22, + 0xda, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, + 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x70, 0x74, 0x68, + 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0d, 0x64, 0x65, 0x70, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x32, + 0x0a, 0x15, 0x63, 0x61, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, + 0x68, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x63, + 0x61, 0x63, 0x68, 0x65, 0x64, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0a, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x69, 0x6e, 0x66, 0x6f, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, + 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x64, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, + 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, 0x46, 0x0a, 0x10, + 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x32, 0x0a, 0x05, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x05, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x22, 0xfc, 0x02, 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, + 0x62, 0x75, 0x67, 0x54, 0x72, 0x61, 0x63, 0x65, 0x12, 0x3b, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, - 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, - 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, - 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x81, 0x01, 0x0a, 0x1a, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x07, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5f, 0x0a, 0x16, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x54, 0x72, + 0x61, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, + 0x52, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x68, 0x61, 0x73, 0x5f, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, + 0x68, 0x61, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, + 0x10, 0x69, 0x73, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x73, 0x43, 0x61, 0x63, 0x68, 0x65, + 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x3f, 0x0a, 0x0c, 0x73, 0x75, 0x62, 0x5f, 0x70, + 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x0b, 0x73, 0x75, 0x62, + 0x50, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x73, 0x22, 0x39, 0x0a, 0x0c, 0x52, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, + 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x4c, 0x41, 0x54, 0x49, 0x4f, + 0x4e, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x45, 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, + 0x4e, 0x10, 0x02, 0x32, 0xa0, 0x04, 0x0a, 0x0f, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x70, 0x61, + 0x74, 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x21, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, + 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x64, 0x69, + 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x5b, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, 0x70, + 0x61, 0x6e, 0x64, 0x12, 0x22, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, + 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, + 0x70, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, + 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, + 0x12, 0x22, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, + 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, + 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x81, 0x01, 0x0a, 0x1a, + 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62, 0x6c, + 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2e, 0x2e, 0x64, 0x69, 0x73, + 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, + 0x68, 0x52, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x64, 0x69, 0x73, + 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, 0x63, 0x68, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x12, 0x2e, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, 0x63, 0x68, 0x61, - 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, 0x63, 0x68, 0x61, - 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x75, 0x0a, 0x16, 0x44, 0x69, 0x73, 0x70, - 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x73, 0x12, 0x2a, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, - 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, - 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, - 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, - 0x70, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, - 0xaa, 0x01, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, - 0x2e, 0x76, 0x31, 0x42, 0x0d, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x65, 0x64, 0x2f, 0x73, 0x70, 0x69, 0x63, 0x65, 0x64, 0x62, - 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, - 0x74, 0x63, 0x68, 0x2f, 0x76, 0x31, 0x3b, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x76, - 0x31, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x0b, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, - 0x63, 0x68, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0b, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, - 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x17, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5c, 0x56, - 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0c, - 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, + 0x75, 0x0a, 0x16, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, + 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x2a, 0x2e, 0x64, 0x69, 0x73, 0x70, + 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, + 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x6f, 0x6b, + 0x75, 0x70, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0xaa, 0x01, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x2e, 0x64, + 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x76, 0x31, 0x42, 0x0d, 0x44, 0x69, 0x73, 0x70, + 0x61, 0x74, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3b, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x65, 0x64, 0x2f, + 0x73, 0x70, 0x69, 0x63, 0x65, 0x64, 0x62, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2f, 0x76, 0x31, 0x3b, 0x64, 0x69, + 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, + 0x0b, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0b, 0x44, + 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x17, 0x44, 0x69, 0x73, + 0x70, 0x61, 0x74, 0x63, 0x68, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0c, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x3a, + 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1505,7 +1565,7 @@ func file_dispatch_v1_dispatch_proto_rawDescGZIP() []byte { } var file_dispatch_v1_dispatch_proto_enumTypes = make([]protoimpl.EnumInfo, 5) -var file_dispatch_v1_dispatch_proto_msgTypes = make([]protoimpl.MessageInfo, 15) +var file_dispatch_v1_dispatch_proto_msgTypes = make([]protoimpl.MessageInfo, 16) var file_dispatch_v1_dispatch_proto_goTypes = []interface{}{ (DispatchCheckRequest_DebugSetting)(0), // 0: dispatch.v1.DispatchCheckRequest.DebugSetting (DispatchCheckResponse_Membership)(0), // 1: dispatch.v1.DispatchCheckResponse.Membership @@ -1522,64 +1582,66 @@ var file_dispatch_v1_dispatch_proto_goTypes = []interface{}{ (*ReachableResource)(nil), // 12: dispatch.v1.ReachableResource (*DispatchReachableResourcesResponse)(nil), // 13: dispatch.v1.DispatchReachableResourcesResponse (*DispatchLookupSubjectsRequest)(nil), // 14: dispatch.v1.DispatchLookupSubjectsRequest - (*DispatchLookupSubjectsResponse)(nil), // 15: dispatch.v1.DispatchLookupSubjectsResponse - (*ResolverMeta)(nil), // 16: dispatch.v1.ResolverMeta - (*ResponseMeta)(nil), // 17: dispatch.v1.ResponseMeta - (*DebugInformation)(nil), // 18: dispatch.v1.DebugInformation - (*CheckDebugTrace)(nil), // 19: dispatch.v1.CheckDebugTrace - (*v1.ObjectAndRelation)(nil), // 20: core.v1.ObjectAndRelation - (*v1.RelationTupleTreeNode)(nil), // 21: core.v1.RelationTupleTreeNode - (*v1.RelationReference)(nil), // 22: core.v1.RelationReference + (*FoundSubject)(nil), // 15: dispatch.v1.FoundSubject + (*DispatchLookupSubjectsResponse)(nil), // 16: dispatch.v1.DispatchLookupSubjectsResponse + (*ResolverMeta)(nil), // 17: dispatch.v1.ResolverMeta + (*ResponseMeta)(nil), // 18: dispatch.v1.ResponseMeta + (*DebugInformation)(nil), // 19: dispatch.v1.DebugInformation + (*CheckDebugTrace)(nil), // 20: dispatch.v1.CheckDebugTrace + (*v1.ObjectAndRelation)(nil), // 21: core.v1.ObjectAndRelation + (*v1.RelationTupleTreeNode)(nil), // 22: core.v1.RelationTupleTreeNode + (*v1.RelationReference)(nil), // 23: core.v1.RelationReference } var file_dispatch_v1_dispatch_proto_depIdxs = []int32{ - 16, // 0: dispatch.v1.DispatchCheckRequest.metadata:type_name -> dispatch.v1.ResolverMeta - 20, // 1: dispatch.v1.DispatchCheckRequest.resource_and_relation:type_name -> core.v1.ObjectAndRelation - 20, // 2: dispatch.v1.DispatchCheckRequest.subject:type_name -> core.v1.ObjectAndRelation + 17, // 0: dispatch.v1.DispatchCheckRequest.metadata:type_name -> dispatch.v1.ResolverMeta + 21, // 1: dispatch.v1.DispatchCheckRequest.resource_and_relation:type_name -> core.v1.ObjectAndRelation + 21, // 2: dispatch.v1.DispatchCheckRequest.subject:type_name -> core.v1.ObjectAndRelation 0, // 3: dispatch.v1.DispatchCheckRequest.debug:type_name -> dispatch.v1.DispatchCheckRequest.DebugSetting - 17, // 4: dispatch.v1.DispatchCheckResponse.metadata:type_name -> dispatch.v1.ResponseMeta + 18, // 4: dispatch.v1.DispatchCheckResponse.metadata:type_name -> dispatch.v1.ResponseMeta 1, // 5: dispatch.v1.DispatchCheckResponse.membership:type_name -> dispatch.v1.DispatchCheckResponse.Membership - 16, // 6: dispatch.v1.DispatchExpandRequest.metadata:type_name -> dispatch.v1.ResolverMeta - 20, // 7: dispatch.v1.DispatchExpandRequest.resource_and_relation:type_name -> core.v1.ObjectAndRelation + 17, // 6: dispatch.v1.DispatchExpandRequest.metadata:type_name -> dispatch.v1.ResolverMeta + 21, // 7: dispatch.v1.DispatchExpandRequest.resource_and_relation:type_name -> core.v1.ObjectAndRelation 2, // 8: dispatch.v1.DispatchExpandRequest.expansion_mode:type_name -> dispatch.v1.DispatchExpandRequest.ExpansionMode - 17, // 9: dispatch.v1.DispatchExpandResponse.metadata:type_name -> dispatch.v1.ResponseMeta - 21, // 10: dispatch.v1.DispatchExpandResponse.tree_node:type_name -> core.v1.RelationTupleTreeNode - 16, // 11: dispatch.v1.DispatchLookupRequest.metadata:type_name -> dispatch.v1.ResolverMeta - 22, // 12: dispatch.v1.DispatchLookupRequest.object_relation:type_name -> core.v1.RelationReference - 20, // 13: dispatch.v1.DispatchLookupRequest.subject:type_name -> core.v1.ObjectAndRelation - 22, // 14: dispatch.v1.DispatchLookupRequest.direct_stack:type_name -> core.v1.RelationReference - 22, // 15: dispatch.v1.DispatchLookupRequest.ttu_stack:type_name -> core.v1.RelationReference - 17, // 16: dispatch.v1.DispatchLookupResponse.metadata:type_name -> dispatch.v1.ResponseMeta - 20, // 17: dispatch.v1.DispatchLookupResponse.resolved_onrs:type_name -> core.v1.ObjectAndRelation - 16, // 18: dispatch.v1.DispatchReachableResourcesRequest.metadata:type_name -> dispatch.v1.ResolverMeta - 22, // 19: dispatch.v1.DispatchReachableResourcesRequest.resource_relation:type_name -> core.v1.RelationReference - 22, // 20: dispatch.v1.DispatchReachableResourcesRequest.subject_relation:type_name -> core.v1.RelationReference + 18, // 9: dispatch.v1.DispatchExpandResponse.metadata:type_name -> dispatch.v1.ResponseMeta + 22, // 10: dispatch.v1.DispatchExpandResponse.tree_node:type_name -> core.v1.RelationTupleTreeNode + 17, // 11: dispatch.v1.DispatchLookupRequest.metadata:type_name -> dispatch.v1.ResolverMeta + 23, // 12: dispatch.v1.DispatchLookupRequest.object_relation:type_name -> core.v1.RelationReference + 21, // 13: dispatch.v1.DispatchLookupRequest.subject:type_name -> core.v1.ObjectAndRelation + 23, // 14: dispatch.v1.DispatchLookupRequest.direct_stack:type_name -> core.v1.RelationReference + 23, // 15: dispatch.v1.DispatchLookupRequest.ttu_stack:type_name -> core.v1.RelationReference + 18, // 16: dispatch.v1.DispatchLookupResponse.metadata:type_name -> dispatch.v1.ResponseMeta + 21, // 17: dispatch.v1.DispatchLookupResponse.resolved_onrs:type_name -> core.v1.ObjectAndRelation + 17, // 18: dispatch.v1.DispatchReachableResourcesRequest.metadata:type_name -> dispatch.v1.ResolverMeta + 23, // 19: dispatch.v1.DispatchReachableResourcesRequest.resource_relation:type_name -> core.v1.RelationReference + 23, // 20: dispatch.v1.DispatchReachableResourcesRequest.subject_relation:type_name -> core.v1.RelationReference 3, // 21: dispatch.v1.ReachableResource.result_status:type_name -> dispatch.v1.ReachableResource.ResultStatus 12, // 22: dispatch.v1.DispatchReachableResourcesResponse.resource:type_name -> dispatch.v1.ReachableResource - 17, // 23: dispatch.v1.DispatchReachableResourcesResponse.metadata:type_name -> dispatch.v1.ResponseMeta - 16, // 24: dispatch.v1.DispatchLookupSubjectsRequest.metadata:type_name -> dispatch.v1.ResolverMeta - 22, // 25: dispatch.v1.DispatchLookupSubjectsRequest.resource_relation:type_name -> core.v1.RelationReference - 22, // 26: dispatch.v1.DispatchLookupSubjectsRequest.subject_relation:type_name -> core.v1.RelationReference - 17, // 27: dispatch.v1.DispatchLookupSubjectsResponse.metadata:type_name -> dispatch.v1.ResponseMeta - 18, // 28: dispatch.v1.ResponseMeta.debug_info:type_name -> dispatch.v1.DebugInformation - 19, // 29: dispatch.v1.DebugInformation.check:type_name -> dispatch.v1.CheckDebugTrace - 5, // 30: dispatch.v1.CheckDebugTrace.request:type_name -> dispatch.v1.DispatchCheckRequest - 4, // 31: dispatch.v1.CheckDebugTrace.resource_relation_type:type_name -> dispatch.v1.CheckDebugTrace.RelationType - 19, // 32: dispatch.v1.CheckDebugTrace.sub_problems:type_name -> dispatch.v1.CheckDebugTrace - 5, // 33: dispatch.v1.DispatchService.DispatchCheck:input_type -> dispatch.v1.DispatchCheckRequest - 7, // 34: dispatch.v1.DispatchService.DispatchExpand:input_type -> dispatch.v1.DispatchExpandRequest - 9, // 35: dispatch.v1.DispatchService.DispatchLookup:input_type -> dispatch.v1.DispatchLookupRequest - 11, // 36: dispatch.v1.DispatchService.DispatchReachableResources:input_type -> dispatch.v1.DispatchReachableResourcesRequest - 14, // 37: dispatch.v1.DispatchService.DispatchLookupSubjects:input_type -> dispatch.v1.DispatchLookupSubjectsRequest - 6, // 38: dispatch.v1.DispatchService.DispatchCheck:output_type -> dispatch.v1.DispatchCheckResponse - 8, // 39: dispatch.v1.DispatchService.DispatchExpand:output_type -> dispatch.v1.DispatchExpandResponse - 10, // 40: dispatch.v1.DispatchService.DispatchLookup:output_type -> dispatch.v1.DispatchLookupResponse - 13, // 41: dispatch.v1.DispatchService.DispatchReachableResources:output_type -> dispatch.v1.DispatchReachableResourcesResponse - 15, // 42: dispatch.v1.DispatchService.DispatchLookupSubjects:output_type -> dispatch.v1.DispatchLookupSubjectsResponse - 38, // [38:43] is the sub-list for method output_type - 33, // [33:38] is the sub-list for method input_type - 33, // [33:33] is the sub-list for extension type_name - 33, // [33:33] is the sub-list for extension extendee - 0, // [0:33] is the sub-list for field type_name + 18, // 23: dispatch.v1.DispatchReachableResourcesResponse.metadata:type_name -> dispatch.v1.ResponseMeta + 17, // 24: dispatch.v1.DispatchLookupSubjectsRequest.metadata:type_name -> dispatch.v1.ResolverMeta + 23, // 25: dispatch.v1.DispatchLookupSubjectsRequest.resource_relation:type_name -> core.v1.RelationReference + 23, // 26: dispatch.v1.DispatchLookupSubjectsRequest.subject_relation:type_name -> core.v1.RelationReference + 15, // 27: dispatch.v1.DispatchLookupSubjectsResponse.found_subjects:type_name -> dispatch.v1.FoundSubject + 18, // 28: dispatch.v1.DispatchLookupSubjectsResponse.metadata:type_name -> dispatch.v1.ResponseMeta + 19, // 29: dispatch.v1.ResponseMeta.debug_info:type_name -> dispatch.v1.DebugInformation + 20, // 30: dispatch.v1.DebugInformation.check:type_name -> dispatch.v1.CheckDebugTrace + 5, // 31: dispatch.v1.CheckDebugTrace.request:type_name -> dispatch.v1.DispatchCheckRequest + 4, // 32: dispatch.v1.CheckDebugTrace.resource_relation_type:type_name -> dispatch.v1.CheckDebugTrace.RelationType + 20, // 33: dispatch.v1.CheckDebugTrace.sub_problems:type_name -> dispatch.v1.CheckDebugTrace + 5, // 34: dispatch.v1.DispatchService.DispatchCheck:input_type -> dispatch.v1.DispatchCheckRequest + 7, // 35: dispatch.v1.DispatchService.DispatchExpand:input_type -> dispatch.v1.DispatchExpandRequest + 9, // 36: dispatch.v1.DispatchService.DispatchLookup:input_type -> dispatch.v1.DispatchLookupRequest + 11, // 37: dispatch.v1.DispatchService.DispatchReachableResources:input_type -> dispatch.v1.DispatchReachableResourcesRequest + 14, // 38: dispatch.v1.DispatchService.DispatchLookupSubjects:input_type -> dispatch.v1.DispatchLookupSubjectsRequest + 6, // 39: dispatch.v1.DispatchService.DispatchCheck:output_type -> dispatch.v1.DispatchCheckResponse + 8, // 40: dispatch.v1.DispatchService.DispatchExpand:output_type -> dispatch.v1.DispatchExpandResponse + 10, // 41: dispatch.v1.DispatchService.DispatchLookup:output_type -> dispatch.v1.DispatchLookupResponse + 13, // 42: dispatch.v1.DispatchService.DispatchReachableResources:output_type -> dispatch.v1.DispatchReachableResourcesResponse + 16, // 43: dispatch.v1.DispatchService.DispatchLookupSubjects:output_type -> dispatch.v1.DispatchLookupSubjectsResponse + 39, // [39:44] is the sub-list for method output_type + 34, // [34:39] is the sub-list for method input_type + 34, // [34:34] is the sub-list for extension type_name + 34, // [34:34] is the sub-list for extension extendee + 0, // [0:34] is the sub-list for field type_name } func init() { file_dispatch_v1_dispatch_proto_init() } @@ -1709,7 +1771,7 @@ func file_dispatch_v1_dispatch_proto_init() { } } file_dispatch_v1_dispatch_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DispatchLookupSubjectsResponse); i { + switch v := v.(*FoundSubject); i { case 0: return &v.state case 1: @@ -1721,7 +1783,7 @@ func file_dispatch_v1_dispatch_proto_init() { } } file_dispatch_v1_dispatch_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ResolverMeta); i { + switch v := v.(*DispatchLookupSubjectsResponse); i { case 0: return &v.state case 1: @@ -1733,7 +1795,7 @@ func file_dispatch_v1_dispatch_proto_init() { } } file_dispatch_v1_dispatch_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ResponseMeta); i { + switch v := v.(*ResolverMeta); i { case 0: return &v.state case 1: @@ -1745,7 +1807,7 @@ func file_dispatch_v1_dispatch_proto_init() { } } file_dispatch_v1_dispatch_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DebugInformation); i { + switch v := v.(*ResponseMeta); i { case 0: return &v.state case 1: @@ -1757,6 +1819,18 @@ func file_dispatch_v1_dispatch_proto_init() { } } file_dispatch_v1_dispatch_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DebugInformation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_dispatch_v1_dispatch_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CheckDebugTrace); i { case 0: return &v.state @@ -1775,7 +1849,7 @@ func file_dispatch_v1_dispatch_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_dispatch_v1_dispatch_proto_rawDesc, NumEnums: 5, - NumMessages: 15, + NumMessages: 16, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/proto/dispatch/v1/dispatch.pb.validate.go b/pkg/proto/dispatch/v1/dispatch.pb.validate.go index 89791f4a68..77b32dcf2f 100644 --- a/pkg/proto/dispatch/v1/dispatch.pb.validate.go +++ b/pkg/proto/dispatch/v1/dispatch.pb.validate.go @@ -1910,6 +1910,107 @@ var _ interface { ErrorName() string } = DispatchLookupSubjectsRequestValidationError{} +// Validate checks the field values on FoundSubject with the rules defined in +// the proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *FoundSubject) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on FoundSubject with the rules defined +// in the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in FoundSubjectMultiError, or +// nil if none found. +func (m *FoundSubject) ValidateAll() error { + return m.validate(true) +} + +func (m *FoundSubject) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for SubjectId + + if len(errors) > 0 { + return FoundSubjectMultiError(errors) + } + + return nil +} + +// FoundSubjectMultiError is an error wrapping multiple validation errors +// returned by FoundSubject.ValidateAll() if the designated constraints aren't met. +type FoundSubjectMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m FoundSubjectMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m FoundSubjectMultiError) AllErrors() []error { return m } + +// FoundSubjectValidationError is the validation error returned by +// FoundSubject.Validate if the designated constraints aren't met. +type FoundSubjectValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e FoundSubjectValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e FoundSubjectValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e FoundSubjectValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e FoundSubjectValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e FoundSubjectValidationError) ErrorName() string { return "FoundSubjectValidationError" } + +// Error satisfies the builtin error interface +func (e FoundSubjectValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sFoundSubject.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = FoundSubjectValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = FoundSubjectValidationError{} + // Validate checks the field values on DispatchLookupSubjectsResponse with the // rules defined in the proto definition for this message. If any rules are // violated, the first error encountered is returned, or nil if there are no violations. @@ -1932,6 +2033,40 @@ func (m *DispatchLookupSubjectsResponse) validate(all bool) error { var errors []error + for idx, item := range m.GetFoundSubjects() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, DispatchLookupSubjectsResponseValidationError{ + field: fmt.Sprintf("FoundSubjects[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, DispatchLookupSubjectsResponseValidationError{ + field: fmt.Sprintf("FoundSubjects[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return DispatchLookupSubjectsResponseValidationError{ + field: fmt.Sprintf("FoundSubjects[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + if all { switch v := interface{}(m.GetMetadata()).(type) { case interface{ ValidateAll() error }: diff --git a/pkg/proto/impl/v1/impl.pb.go b/pkg/proto/impl/v1/impl.pb.go index 125e9d503c..297bee8ac3 100644 --- a/pkg/proto/impl/v1/impl.pb.go +++ b/pkg/proto/impl/v1/impl.pb.go @@ -76,6 +76,7 @@ type DecodedZookie struct { Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` // Types that are assignable to VersionOneof: + // // *DecodedZookie_V1 // *DecodedZookie_V2 VersionOneof isDecodedZookie_VersionOneof `protobuf_oneof:"version_oneof"` @@ -163,6 +164,7 @@ type DecodedZedToken struct { unknownFields protoimpl.UnknownFields // Types that are assignable to VersionOneof: + // // *DecodedZedToken_DeprecatedV1Zookie // *DecodedZedToken_V1 VersionOneof isDecodedZedToken_VersionOneof `protobuf_oneof:"version_oneof"` diff --git a/pkg/tuple/onrbytypeset.go b/pkg/tuple/onrbytypeset.go index e9493c5a70..7b2049eafd 100644 --- a/pkg/tuple/onrbytypeset.go +++ b/pkg/tuple/onrbytypeset.go @@ -54,6 +54,9 @@ func (s *ONRByTypeSet) Map(mapper func(rr *core.RelationReference) (*core.Relati if err != nil { return nil, err } + if updatedType == nil { + continue + } updatedTypeKey := fmt.Sprintf("%s#%s", updatedType.Namespace, updatedType.Relation) mapped.byType[updatedTypeKey] = objectIds } diff --git a/proto/internal/dispatch/v1/dispatch.proto b/proto/internal/dispatch/v1/dispatch.proto index b9df367033..13dd4e6aff 100644 --- a/proto/internal/dispatch/v1/dispatch.proto +++ b/proto/internal/dispatch/v1/dispatch.proto @@ -123,8 +123,13 @@ message DispatchLookupSubjectsRequest { [ (validate.rules).message.required = true ]; } +message FoundSubject { + string subject_id = 1; + repeated string excluded_subject_ids = 2; +} + message DispatchLookupSubjectsResponse { - repeated string found_subject_ids = 1; + repeated FoundSubject found_subjects = 1; ResponseMeta metadata = 2; } diff --git a/tools/analyzers/go.mod b/tools/analyzers/go.mod index 02d075de80..d38a530a35 100644 --- a/tools/analyzers/go.mod +++ b/tools/analyzers/go.mod @@ -1,14 +1,13 @@ module github.com/authzed/spicedb/tools/analyzers -go 1.18 +go 1.19 require ( github.com/jzelinskie/stringz v0.0.1 - golang.org/x/tools v0.1.10 + golang.org/x/tools v0.1.12 ) require ( - golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect - golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect ) diff --git a/tools/analyzers/go.sum b/tools/analyzers/go.sum index 14e26dad4f..3520b29844 100644 --- a/tools/analyzers/go.sum +++ b/tools/analyzers/go.sum @@ -1,10 +1,8 @@ github.com/jzelinskie/stringz v0.0.1 h1:IahR+y8ct2nyj7B6i8UtFsGFj4ex1SX27iKFYsAheLk= github.com/jzelinskie/stringz v0.0.1/go.mod h1:hHYbgxJuNLRw91CmpuFsYEOyQqpDVFg8pvEh23vy4P0= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=