File 0002-Drop-zanzana.patch of Package grafana
From 5873f5f7ea2d30ae40f98ecc70230fa82bcade5d Mon Sep 17 00:00:00 2001
From: Witek Bedyk <wbedyk@suse.com>
Date: Thu, 19 Feb 2026 17:00:12 +0100
Subject: [PATCH] Drop Zanzana
Drop experimental authorization server/client implementation which is
affected by CVE-2025-64751.
---
apps/advisor/go.mod | 1 -
apps/advisor/go.sum | 4 -
go.mod | 32 +-
.../backgroundsvcs/background_services.go | 3 -
pkg/server/module_server.go | 5 -
pkg/server/wire.go | 2 -
pkg/server/wire_gen.go | 17 +-
pkg/services/accesscontrol/dualwrite/batch.go | 19 -
.../accesscontrol/dualwrite/collectors.go | 555 ------------------
.../accesscontrol/dualwrite/reconciler.go | 231 --------
.../dualwrite/resource_reconciler.go | 121 ----
pkg/services/authz/wireset.go | 1 -
pkg/services/authz/zanzana.go | 233 --------
pkg/services/authz/zanzana/client.go | 27 -
pkg/services/authz/zanzana/client/client.go | 126 ----
pkg/services/authz/zanzana/client/noop.go | 36 --
pkg/services/authz/zanzana/common/info.go | 149 -----
pkg/services/authz/zanzana/common/tuple.go | 360 ------------
pkg/services/authz/zanzana/logger/logger.go | 96 ---
pkg/services/authz/zanzana/schema/README.md | 61 --
pkg/services/authz/zanzana/schema/schema.go | 31 -
.../authz/zanzana/schema/schema_core.fga | 25 -
.../authz/zanzana/schema/schema_folder.fga | 18 -
.../authz/zanzana/schema/schema_resource.fga | 50 --
.../authz/zanzana/schema/transform.go | 58 --
.../authz/zanzana/schema/transform_test.go | 313 ----------
pkg/services/authz/zanzana/server.go | 30 -
pkg/services/authz/zanzana/server/auth.go | 24 -
pkg/services/authz/zanzana/server/health.go | 77 ---
.../authz/zanzana/server/openfga_server.go | 103 ----
pkg/services/authz/zanzana/server/server.go | 99 ----
.../zanzana/server/server_batch_check.go | 89 ---
.../zanzana/server/server_batch_check_test.go | 193 ------
.../authz/zanzana/server/server_check.go | 157 -----
.../authz/zanzana/server/server_check_test.go | 155 -----
.../authz/zanzana/server/server_list.go | 232 --------
.../authz/zanzana/server/server_list_test.go | 121 ----
.../authz/zanzana/server/server_read.go | 56 --
.../authz/zanzana/server/server_store.go | 104 ----
.../authz/zanzana/server/server_test.go | 124 ----
.../authz/zanzana/server/server_write.go | 56 --
pkg/services/authz/zanzana/store.go | 18 -
.../authz/zanzana/store/migration/migrator.go | 128 ----
pkg/services/authz/zanzana/store/store.go | 125 ----
pkg/services/authz/zanzana/translations.go | 81 ---
pkg/services/authz/zanzana/zanzana.go | 182 ------
pkg/storage/unified/apistore/go.mod | 34 --
pkg/storage/unified/apistore/go.sum | 106 ----
48 files changed, 8 insertions(+), 4860 deletions(-)
delete mode 100644 pkg/services/accesscontrol/dualwrite/batch.go
delete mode 100644 pkg/services/accesscontrol/dualwrite/collectors.go
delete mode 100644 pkg/services/accesscontrol/dualwrite/reconciler.go
delete mode 100644 pkg/services/accesscontrol/dualwrite/resource_reconciler.go
delete mode 100644 pkg/services/authz/zanzana.go
delete mode 100644 pkg/services/authz/zanzana/client.go
delete mode 100644 pkg/services/authz/zanzana/client/client.go
delete mode 100644 pkg/services/authz/zanzana/client/noop.go
delete mode 100644 pkg/services/authz/zanzana/common/info.go
delete mode 100644 pkg/services/authz/zanzana/common/tuple.go
delete mode 100644 pkg/services/authz/zanzana/logger/logger.go
delete mode 100644 pkg/services/authz/zanzana/schema/README.md
delete mode 100644 pkg/services/authz/zanzana/schema/schema.go
delete mode 100644 pkg/services/authz/zanzana/schema/schema_core.fga
delete mode 100644 pkg/services/authz/zanzana/schema/schema_folder.fga
delete mode 100644 pkg/services/authz/zanzana/schema/schema_resource.fga
delete mode 100644 pkg/services/authz/zanzana/schema/transform.go
delete mode 100644 pkg/services/authz/zanzana/schema/transform_test.go
delete mode 100644 pkg/services/authz/zanzana/server.go
delete mode 100644 pkg/services/authz/zanzana/server/auth.go
delete mode 100644 pkg/services/authz/zanzana/server/health.go
delete mode 100644 pkg/services/authz/zanzana/server/openfga_server.go
delete mode 100644 pkg/services/authz/zanzana/server/server.go
delete mode 100644 pkg/services/authz/zanzana/server/server_batch_check.go
delete mode 100644 pkg/services/authz/zanzana/server/server_batch_check_test.go
delete mode 100644 pkg/services/authz/zanzana/server/server_check.go
delete mode 100644 pkg/services/authz/zanzana/server/server_check_test.go
delete mode 100644 pkg/services/authz/zanzana/server/server_list.go
delete mode 100644 pkg/services/authz/zanzana/server/server_list_test.go
delete mode 100644 pkg/services/authz/zanzana/server/server_read.go
delete mode 100644 pkg/services/authz/zanzana/server/server_store.go
delete mode 100644 pkg/services/authz/zanzana/server/server_test.go
delete mode 100644 pkg/services/authz/zanzana/server/server_write.go
delete mode 100644 pkg/services/authz/zanzana/store.go
delete mode 100644 pkg/services/authz/zanzana/store/migration/migrator.go
delete mode 100644 pkg/services/authz/zanzana/store/store.go
delete mode 100644 pkg/services/authz/zanzana/translations.go
delete mode 100644 pkg/services/authz/zanzana/zanzana.go
diff --git a/apps/advisor/go.mod b/apps/advisor/go.mod
index 686b20436d4..6f89d5d489a 100644
--- a/apps/advisor/go.mod
+++ b/apps/advisor/go.mod
@@ -45,7 +45,6 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
- github.com/openfga/openfga v1.8.13 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
diff --git a/apps/advisor/go.sum b/apps/advisor/go.sum
index c287d669828..e5282e7e32f 100644
--- a/apps/advisor/go.sum
+++ b/apps/advisor/go.sum
@@ -102,10 +102,6 @@ github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
-github.com/openfga/openfga v1.8.12 h1:xEirA6tFwaJfjBDtbHWCK0/Tw+B8XleRyhg9dcEpzHo=
-github.com/openfga/openfga v1.8.12/go.mod h1:fIZyekdNB+tWQ6zIiglZonAc5ErZiDGMeHue/BzRYRM=
-github.com/openfga/openfga v1.8.13 h1:ROURkotKhbmtyBX3188+cNElN8AOZmTl0CMkxUqwawo=
-github.com/openfga/openfga v1.8.13/go.mod h1:h1VGcVW81eY1YyDtFx5+gxxAIEhIiOGR9SRGgs/X/k8=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
diff --git a/go.mod b/go.mod
index 528ca00f6c6..397e7069fc3 100644
--- a/go.mod
+++ b/go.mod
@@ -97,7 +97,7 @@ require (
github.com/grafana/tempo v1.5.1-0.20241001135150-ed943d7a56b2 // @grafana/observability-traces-and-profiling
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // @grafana/plugins-platform-backend
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // @grafana/grafana-backend-group
- github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // @grafana/identity-access-team
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect; @grafana/identity-access-team
github.com/hashicorp/go-hclog v1.6.3 // @grafana/plugins-platform-backend
github.com/hashicorp/go-multierror v1.1.1 // @grafana/alerting-squad
github.com/hashicorp/go-plugin v1.6.3 // @grafana/plugins-platform-backend
@@ -126,9 +126,6 @@ require (
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // @grafana/alerting-backend
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // @grafana/grafana-operator-experience-squad
github.com/olekukonko/tablewriter v0.0.5 // @grafana/grafana-backend-group
- github.com/openfga/api/proto v0.0.0-20250127102726-f9709139a369 // @grafana/identity-access-team
- github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20250220223040-ed0cfba54336 // @grafana/identity-access-team
- github.com/openfga/openfga v1.8.13 // @grafana/identity-access-team
github.com/openzipkin/zipkin-go v0.4.3 // @grafana/oss-big-tent
github.com/patrickmn/go-cache v2.1.0+incompatible // @grafana/alerting-backend
github.com/phpdave11/gofpdi v1.0.14 // @grafana/sharing-squad
@@ -139,7 +136,7 @@ require (
github.com/prometheus/prometheus v0.301.0 // @grafana/alerting-backend
github.com/redis/go-redis/v9 v9.7.3 // @grafana/alerting-backend
github.com/robfig/cron/v3 v3.0.1 // @grafana/grafana-backend-group
- github.com/rs/cors v1.11.1 // @grafana/identity-access-team
+ github.com/rs/cors v1.11.1 // indirect; @grafana/identity-access-team
github.com/russellhaering/goxmldsig v1.4.0 // @grafana/grafana-backend-group
github.com/spf13/cobra v1.9.1 // @grafana/grafana-app-platform-squad
github.com/spf13/pflag v1.0.6 // @grafana-app-platform-squad
@@ -167,7 +164,7 @@ require (
go.opentelemetry.io/otel/trace v1.36.0 // @grafana/grafana-backend-group
go.uber.org/atomic v1.11.0 // @grafana/alerting-backend
go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage
- go.uber.org/zap v1.27.0 // @grafana/identity-access-team
+ go.uber.org/zap v1.27.0 // indirect; @grafana/identity-access-team
gocloud.dev v0.40.0 // @grafana/grafana-app-platform-squad
golang.org/x/crypto v0.45.0 // @grafana/grafana-backend-group
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // @grafana/alerting-backend
@@ -252,10 +249,8 @@ require (
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
- github.com/Masterminds/squirrel v1.5.4 // indirect
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/RoaringBitmap/roaring v1.9.3 // indirect
- github.com/Yiling-J/theine-go v0.6.1 // indirect
github.com/agext/levenshtein v1.2.1 // indirect
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
@@ -337,7 +332,6 @@ require (
github.com/elazarl/goproxy v1.7.1 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/emicklei/proto v1.13.2 // indirect
- github.com/emirpasic/gods v1.18.1 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
@@ -391,10 +385,6 @@ require (
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
- github.com/jackc/pgpassfile v1.0.0 // indirect
- github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
- github.com/jackc/pgx/v5 v5.7.5 // indirect
- github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
@@ -412,8 +402,6 @@ require (
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
- github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
- github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lestrrat-go/strftime v1.0.4 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 // indirect
@@ -425,7 +413,6 @@ require (
github.com/maypok86/otter v1.2.4 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/mdlayher/vsock v1.2.1 // indirect
- github.com/mfridman/interpolate v0.0.2 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect
@@ -443,14 +430,12 @@ require (
github.com/mschoch/smat v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
- github.com/natefinch/wrap v0.2.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/oapi-codegen/runtime v1.0.0 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
- github.com/oklog/ulid/v2 v2.1.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e // @grafana/grafana-search-and-storage
@@ -464,7 +449,6 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
- github.com/pressly/goose/v3 v3.24.3 // indirect
github.com/prometheus/common/sigv4 v0.1.0 // indirect
github.com/prometheus/exporter-toolkit v0.13.2 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
@@ -476,26 +460,20 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
- github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/segmentio/encoding v0.4.1 // indirect
github.com/sercand/kuberesolver/v5 v5.1.1 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
- github.com/sethvargo/go-retry v0.3.0 // indirect
github.com/shadowspore/fossil-delta v0.0.0-20241213113458-1d797d70cbe3 // indirect
github.com/shopspring/decimal v1.4.0 // @grafana/grafana-datasources-core-services
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sony/gobreaker v0.5.0 // indirect
- github.com/sourcegraph/conc v0.3.0 // indirect
- github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
- github.com/spf13/viper v1.20.1 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
- github.com/subosito/gotenv v1.6.0 // indirect
github.com/tetratelabs/wazero v1.8.2 // indirect
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
@@ -524,7 +502,6 @@ require (
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
- go.uber.org/mock v0.5.2 // indirect
go.uber.org/multierr v1.11.0 // indirect
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect
golang.org/x/sys v0.38.0 // indirect
@@ -573,9 +550,10 @@ require (
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
github.com/bluele/gcache v0.0.2 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
+ github.com/docker/docker v28.1.1+incompatible // indirect
+ github.com/docker/go-connections v0.5.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/go-jose/go-jose/v4 v4.1.0 // indirect
- github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
diff --git a/pkg/registry/backgroundsvcs/background_services.go b/pkg/registry/backgroundsvcs/background_services.go
index b0f339ed29d..55d63a1f3d3 100644
--- a/pkg/registry/backgroundsvcs/background_services.go
+++ b/pkg/registry/backgroundsvcs/background_services.go
@@ -10,7 +10,6 @@ import (
"github.com/grafana/grafana/pkg/registry"
apiregistry "github.com/grafana/grafana/pkg/registry/apis"
appregistry "github.com/grafana/grafana/pkg/registry/apps"
- "github.com/grafana/grafana/pkg/services/accesscontrol/dualwrite"
"github.com/grafana/grafana/pkg/services/anonymous/anonimpl"
grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/auth"
@@ -66,7 +65,6 @@ func ProvideBackgroundServiceRegistry(
ssoSettings *ssosettingsimpl.Service,
pluginExternal *pluginexternal.Service,
pluginInstaller *plugininstaller.Service,
- zanzanaReconciler *dualwrite.ZanzanaReconciler,
appRegistry *appregistry.Service,
pluginDashboardUpdater *plugindashboardsservice.DashboardUpdater,
// Need to make sure these are initialized, is there a better place to put them?
@@ -112,7 +110,6 @@ func ProvideBackgroundServiceRegistry(
ssoSettings,
pluginExternal,
pluginInstaller,
- zanzanaReconciler,
appRegistry,
pluginDashboardUpdater,
)
diff --git a/pkg/server/module_server.go b/pkg/server/module_server.go
index 2f0e7a1a03c..480111f4e1f 100644
--- a/pkg/server/module_server.go
+++ b/pkg/server/module_server.go
@@ -13,7 +13,6 @@ import (
"github.com/grafana/grafana/pkg/api"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/modules"
- "github.com/grafana/grafana/pkg/services/authz"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/unified/resource"
@@ -141,10 +140,6 @@ func (s *ModuleServer) Run() error {
return sql.ProvideUnifiedStorageGrpcService(s.cfg, s.features, nil, s.log, nil, docBuilders, s.storageMetrics)
})
- m.RegisterModule(modules.ZanzanaServer, func() (services.Service, error) {
- return authz.ProvideZanzanaService(s.cfg, s.features)
- })
-
m.RegisterModule(modules.All, nil)
return m.Run(s.context)
diff --git a/pkg/server/wire.go b/pkg/server/wire.go
index b7b536a4200..d4310fea32d 100644
--- a/pkg/server/wire.go
+++ b/pkg/server/wire.go
@@ -40,7 +40,6 @@ import (
appregistry "github.com/grafana/grafana/pkg/registry/apps"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
- "github.com/grafana/grafana/pkg/services/accesscontrol/dualwrite"
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/permreg"
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
@@ -372,7 +371,6 @@ var wireBasicSet = wire.NewSet(
wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)),
permreg.ProvidePermissionRegistry,
acimpl.ProvideAccessControl,
- dualwrite.ProvideZanzanaReconciler,
navtreeimpl.ProvideService,
wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)),
wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)),
diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go
index 499b5f90199..0deaf09d28c 100644
--- a/pkg/server/wire_gen.go
+++ b/pkg/server/wire_gen.go
@@ -66,7 +66,6 @@ import (
"github.com/grafana/grafana/pkg/registry/usagestatssvcs"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
- dualwrite2 "github.com/grafana/grafana/pkg/services/accesscontrol/dualwrite"
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/permreg"
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
@@ -659,11 +658,6 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser
if err != nil {
return nil, err
}
- client, err := authz.ProvideZanzana(cfg, sqlStore, tracingService, featureToggles)
- if err != nil {
- return nil, err
- }
- zanzanaReconciler := dualwrite2.ProvideZanzanaReconciler(cfg, featureToggles, client, sqlStore, serverLockService, folderimplService)
playlistAppProvider := playlist.RegisterApp(playlistService, cfg, featureToggles)
investigationsAppProvider := investigations.RegisterApp(cfg)
checkregistryService := checkregistry.ProvideService(service12, pluginstoreService, plugincontextProvider, middlewareHandler, repoManager, preinstallImpl, noop)
@@ -732,7 +726,7 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser
}
ossUserProtectionImpl := authinfoimpl.ProvideOSSUserProtectionService()
registration := authnimpl.ProvideRegistration(cfg, authnService, orgService, userAuthTokenService, acimplService, permissionRegistry, apikeyService, userService, authService, ossUserProtectionImpl, loginattemptimplService, quotaService, authinfoimplService, renderingService, featureToggles, oauthtokenService, socialService, remoteCache, ldapImpl, ossImpl, tracingService, tempuserService, notificationService)
- backgroundServiceRegistry := backgroundsvcs.ProvideBackgroundServiceRegistry(httpServer, alertNG, cleanUpService, grafanaLive, gateway, notificationService, pluginstoreService, renderingService, userAuthTokenService, tracingService, provisioningServiceImpl, usageStats, statscollectorService, grafanaService, pluginsService, internalMetricsService, secretsService, remoteCache, storageService, searchService, entityEventsService, serviceAccountsService, grpcserverProvider, secretMigrationProviderImpl, loginattemptimplService, supportbundlesimplService, metricService, keyRetriever, angulardetectorsproviderDynamic, apiserverService, anonDeviceService, ssosettingsimplService, pluginexternalService, plugininstallerService, zanzanaReconciler, appregistryService, dashboardUpdater, serviceImpl, serviceAccountsProxy, guardianProvider, sanitizerProvider, healthService, reflectionService, apiService, apiregistryService, idimplService, teamAPI, ssosettingsimplService, cloudmigrationService, registration)
+ backgroundServiceRegistry := backgroundsvcs.ProvideBackgroundServiceRegistry(httpServer, alertNG, cleanUpService, grafanaLive, gateway, notificationService, pluginstoreService, renderingService, userAuthTokenService, tracingService, provisioningServiceImpl, usageStats, statscollectorService, grafanaService, pluginsService, internalMetricsService, secretsService, remoteCache, storageService, searchService, entityEventsService, serviceAccountsService, grpcserverProvider, secretMigrationProviderImpl, loginattemptimplService, supportbundlesimplService, metricService, keyRetriever, angulardetectorsproviderDynamic, apiserverService, anonDeviceService, ssosettingsimplService, pluginexternalService, plugininstallerService, appregistryService, dashboardUpdater, serviceImpl, serviceAccountsProxy, guardianProvider, sanitizerProvider, healthService, reflectionService, apiService, apiregistryService, idimplService, teamAPI, ssosettingsimplService, cloudmigrationService, registration)
usageStatsProvidersRegistry := usagestatssvcs.ProvideUsageStatsProvidersRegistry(acimplService, userService)
server, err := New(opts, cfg, httpServer, acimplService, provisioningServiceImpl, backgroundServiceRegistry, usageStatsProvidersRegistry, statscollectorService, registerer)
if err != nil {
@@ -1149,11 +1143,6 @@ func InitializeForTest(t sqlutil.ITestDB, cfg *setting.Cfg, opts Options, apiOpt
if err != nil {
return nil, err
}
- client, err := authz.ProvideZanzana(cfg, sqlStore, tracingService, featureToggles)
- if err != nil {
- return nil, err
- }
- zanzanaReconciler := dualwrite2.ProvideZanzanaReconciler(cfg, featureToggles, client, sqlStore, serverLockService, folderimplService)
playlistAppProvider := playlist.RegisterApp(playlistService, cfg, featureToggles)
investigationsAppProvider := investigations.RegisterApp(cfg)
checkregistryService := checkregistry.ProvideService(service12, pluginstoreService, plugincontextProvider, middlewareHandler, repoManager, preinstallImpl, noop)
@@ -1222,7 +1211,7 @@ func InitializeForTest(t sqlutil.ITestDB, cfg *setting.Cfg, opts Options, apiOpt
}
ossUserProtectionImpl := authinfoimpl.ProvideOSSUserProtectionService()
registration := authnimpl.ProvideRegistration(cfg, authnService, orgService, userAuthTokenService, acimplService, permissionRegistry, apikeyService, userService, authService, ossUserProtectionImpl, loginattemptimplService, quotaService, authinfoimplService, renderingService, featureToggles, oauthtokentestService, socialService, remoteCache, ldapImpl, ossImpl, tracingService, tempuserService, notificationServiceMock)
- backgroundServiceRegistry := backgroundsvcs.ProvideBackgroundServiceRegistry(httpServer, alertNG, cleanUpService, grafanaLive, gateway, notificationService, pluginstoreService, renderingService, userAuthTokenService, tracingService, provisioningServiceImpl, usageStats, statscollectorService, grafanaService, pluginsService, internalMetricsService, secretsService, remoteCache, storageService, searchService, entityEventsService, serviceAccountsService, grpcserverProvider, secretMigrationProviderImpl, loginattemptimplService, supportbundlesimplService, metricService, keyRetriever, angulardetectorsproviderDynamic, apiserverService, anonDeviceService, ssosettingsimplService, pluginexternalService, plugininstallerService, zanzanaReconciler, appregistryService, dashboardUpdater, serviceImpl, serviceAccountsProxy, guardianProvider, sanitizerProvider, healthService, reflectionService, apiService, apiregistryService, idimplService, teamAPI, ssosettingsimplService, cloudmigrationService, registration)
+ backgroundServiceRegistry := backgroundsvcs.ProvideBackgroundServiceRegistry(httpServer, alertNG, cleanUpService, grafanaLive, gateway, notificationService, pluginstoreService, renderingService, userAuthTokenService, tracingService, provisioningServiceImpl, usageStats, statscollectorService, grafanaService, pluginsService, internalMetricsService, secretsService, remoteCache, storageService, searchService, entityEventsService, serviceAccountsService, grpcserverProvider, secretMigrationProviderImpl, loginattemptimplService, supportbundlesimplService, metricService, keyRetriever, angulardetectorsproviderDynamic, apiserverService, anonDeviceService, ssosettingsimplService, pluginexternalService, plugininstallerService, appregistryService, dashboardUpdater, serviceImpl, serviceAccountsProxy, guardianProvider, sanitizerProvider, healthService, reflectionService, apiService, apiregistryService, idimplService, teamAPI, ssosettingsimplService, cloudmigrationService, registration)
usageStatsProvidersRegistry := usagestatssvcs.ProvideUsageStatsProvidersRegistry(acimplService, userService)
server, err := New(opts, cfg, httpServer, acimplService, provisioningServiceImpl, backgroundServiceRegistry, usageStatsProvidersRegistry, statscollectorService, registerer)
if err != nil {
@@ -1357,7 +1346,7 @@ func InitializeDocumentBuilders(cfg *setting.Cfg) (resource.DocumentBuilderSuppl
// wire.go:
-var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatechecker.ProvideGrafanaService, updatechecker.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator2.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service10.ProvideService, wire.Bind(new(service10.LDAP), new(*service10.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service5.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service5.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database4.DashboardSnapshotStore)), database4.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service8.ServiceImpl)), service8.ProvideService, service5.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service5.Service)), service5.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager2.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, featuremgmt.ProvideOpenFeatureService, service6.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service6.DashboardServiceImpl)), service6.ProvideDashboardService, service6.ProvideDashboardProvisioningService, service6.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service9.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service9.ImportDashboardService)), service7.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service7.Service)), service7.ProvideDashboardUpdater, guardian2.ProvideService, sanitizer.ProvideService, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideService, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, interceptors.ProvideAuthenticator, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideMigrateToPluginService, migrations2.ProvideMigrateFromPluginService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, migrator2.NewWithEngine, resource.ProvideStorageMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet)
+var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatechecker.ProvideGrafanaService, updatechecker.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator2.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service10.ProvideService, wire.Bind(new(service10.LDAP), new(*service10.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service5.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service5.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database4.DashboardSnapshotStore)), database4.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service8.ServiceImpl)), service8.ProvideService, service5.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service5.Service)), service5.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager2.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, featuremgmt.ProvideOpenFeatureService, service6.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service6.DashboardServiceImpl)), service6.ProvideDashboardService, service6.ProvideDashboardProvisioningService, service6.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service9.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service9.ImportDashboardService)), service7.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service7.Service)), service7.ProvideDashboardUpdater, guardian2.ProvideService, sanitizer.ProvideService, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideService, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, interceptors.ProvideAuthenticator, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideMigrateToPluginService, migrations2.ProvideMigrateFromPluginService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, migrator2.NewWithEngine, resource.ProvideStorageMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet)
var wireSet = wire.NewSet(
wireBasicSet, metrics.WireSet, sqlstore.ProvideService, metrics2.ProvideService, wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), prefimpl.ProvideService, oauthtoken.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)),
diff --git a/pkg/services/accesscontrol/dualwrite/batch.go b/pkg/services/accesscontrol/dualwrite/batch.go
deleted file mode 100644
index 029cd4da30a..00000000000
--- a/pkg/services/accesscontrol/dualwrite/batch.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package dualwrite
-
-// batch will call fn with a batch of T for specified size.
-func batch[T any](items []T, batchSize int, fn func([]T) error) error {
- count := len(items)
- for i := 0; i < count; {
- end := i + batchSize
- if end > count {
- end = count
- }
-
- if err := fn(items[i:end]); err != nil {
- return err
- }
-
- i = end
- }
- return nil
-}
diff --git a/pkg/services/accesscontrol/dualwrite/collectors.go b/pkg/services/accesscontrol/dualwrite/collectors.go
deleted file mode 100644
index 871df2edf3f..00000000000
--- a/pkg/services/accesscontrol/dualwrite/collectors.go
+++ /dev/null
@@ -1,555 +0,0 @@
-package dualwrite
-
-import (
- "context"
-
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
-
- "github.com/grafana/grafana/pkg/apimachinery/identity"
- "github.com/grafana/grafana/pkg/infra/db"
- authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
- "github.com/grafana/grafana/pkg/services/authz/zanzana"
- "github.com/grafana/grafana/pkg/services/folder"
- "github.com/grafana/grafana/pkg/setting"
-)
-
-func teamMembershipCollector(store db.DB) legacyTupleCollector {
- return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) {
- query := `
- SELECT t.uid as team_uid, u.uid as user_uid, tm.permission
- FROM team_member tm
- INNER JOIN team t ON tm.team_id = t.id
- INNER JOIN ` + store.GetDialect().Quote("user") + ` u ON tm.user_id = u.id
- WHERE t.org_id = ?
- `
-
- type membership struct {
- TeamUID string `xorm:"team_uid"`
- UserUID string `xorm:"user_uid"`
- Permission int
- }
-
- var memberships []membership
- err := store.WithDbSession(ctx, func(sess *db.Session) error {
- return sess.SQL(query, orgID).Find(&memberships)
- })
-
- if err != nil {
- return nil, err
- }
-
- tuples := make(map[string]map[string]*openfgav1.TupleKey)
-
- for _, m := range memberships {
- tuple := &openfgav1.TupleKey{
- User: zanzana.NewTupleEntry(zanzana.TypeUser, m.UserUID, ""),
- Object: zanzana.NewTupleEntry(zanzana.TypeTeam, m.TeamUID, ""),
- }
-
- // Admin permission is 4 and member 0
- if m.Permission == 4 {
- tuple.Relation = zanzana.RelationTeamAdmin
- } else {
- tuple.Relation = zanzana.RelationTeamMember
- }
-
- if tuples[tuple.Object] == nil {
- tuples[tuple.Object] = make(map[string]*openfgav1.TupleKey)
- }
-
- tuples[tuple.Object][tuple.String()] = tuple
- }
-
- return tuples, nil
- }
-}
-
-// folderTreeCollector collects folder tree structure and writes it as relation tuples
-func folderTreeCollector(folderService folder.Service) legacyTupleCollector {
- return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) {
- ctx, span := tracer.Start(ctx, "accesscontrol.migrator.folderTreeCollector")
- defer span.End()
-
- ctx, ident := identity.WithServiceIdentity(ctx, orgID)
-
- q := folder.GetFoldersQuery{
- OrgID: orgID,
- SignedInUser: ident,
- }
-
- folders, err := folderService.GetFolders(ctx, q)
- if err != nil {
- return nil, err
- }
-
- tuples := make(map[string]map[string]*openfgav1.TupleKey)
-
- for _, f := range folders {
- var tuple *openfgav1.TupleKey
- if f.ParentUID == "" {
- continue
- }
-
- tuple = &openfgav1.TupleKey{
- Object: zanzana.NewTupleEntry(zanzana.TypeFolder, f.UID, ""),
- Relation: zanzana.RelationParent,
- User: zanzana.NewTupleEntry(zanzana.TypeFolder, f.ParentUID, ""),
- }
-
- if tuples[tuple.Object] == nil {
- tuples[tuple.Object] = make(map[string]*openfgav1.TupleKey)
- }
-
- tuples[tuple.Object][tuple.String()] = tuple
- }
-
- return tuples, nil
- }
-}
-
-// managedPermissionsCollector collects managed permissions.
-// It will only store actions that are supported by our schema. Managed permissions can
-// be directly mapped to user/team/role without having to write an intermediate role.
-func managedPermissionsCollector(store db.DB, kind string) legacyTupleCollector {
- return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) {
- query := `
- SELECT u.uid as user_uid, u.is_service_account as is_service_account, t.uid as team_uid, p.action, p.kind, p.identifier, r.org_id, br.role as basic_role_name
- FROM permission p
- INNER JOIN role r ON p.role_id = r.id
- LEFT JOIN user_role ur ON r.id = ur.role_id
- LEFT JOIN ` + store.GetDialect().Quote("user") + ` u ON u.id = ur.user_id
- LEFT JOIN team_role tr ON r.id = tr.role_id
- LEFT JOIN team t ON tr.team_id = t.id
- LEFT JOIN builtin_role br ON r.id = br.role_id
- WHERE r.name LIKE 'managed:%'
- AND r.org_id = ?
- AND p.kind = ?
- `
- type Permission struct {
- Action string `xorm:"action"`
- Kind string
- Identifier string
- UserUID string `xorm:"user_uid"`
- IsServiceAccount bool `xorm:"is_service_account"`
- TeamUID string `xorm:"team_uid"`
- BasicRoleName string `xorm:"basic_role_name"`
- }
-
- var permissions []Permission
- err := store.WithDbSession(ctx, func(sess *db.Session) error {
- return sess.SQL(query, orgID, kind).Find(&permissions)
- })
-
- if err != nil {
- return nil, err
- }
-
- tuples := make(map[string]map[string]*openfgav1.TupleKey)
-
- for _, p := range permissions {
- var subject string
- if len(p.UserUID) > 0 && p.IsServiceAccount {
- subject = zanzana.NewTupleEntry(zanzana.TypeServiceAccount, p.UserUID, "")
- } else if len(p.UserUID) > 0 {
- subject = zanzana.NewTupleEntry(zanzana.TypeUser, p.UserUID, "")
- } else if len(p.TeamUID) > 0 {
- subject = zanzana.NewTupleEntry(zanzana.TypeTeam, p.TeamUID, zanzana.RelationTeamMember)
- } else {
- subject = zanzana.NewTupleEntry(zanzana.TypeRole, zanzana.TranslateBasicRole(p.BasicRoleName), zanzana.RelationAssignee)
- }
-
- tuple, ok := zanzana.TranslateToResourceTuple(subject, p.Action, p.Kind, p.Identifier)
- if !ok {
- continue
- }
-
- if tuples[tuple.Object] == nil {
- tuples[tuple.Object] = make(map[string]*openfgav1.TupleKey)
- }
-
- // For resource actions on folders we need to merge the tuples into one with combined
- // group_resources.
- if zanzana.IsFolderResourceTuple(tuple) {
- key := tupleStringWithoutCondition(tuple)
- if t, ok := tuples[tuple.Object][key]; ok {
- zanzana.MergeFolderResourceTuples(t, tuple)
- } else {
- tuples[tuple.Object][key] = tuple
- }
-
- continue
- }
-
- tuples[tuple.Object][tuple.String()] = tuple
- }
-
- return tuples, nil
- }
-}
-
-func tupleStringWithoutCondition(tuple *openfgav1.TupleKey) string {
- c := tuple.Condition
- tuple.Condition = nil
- s := tuple.String()
- tuple.Condition = c
- return s
-}
-
-// basicRoleBindingsCollector collects role bindings for basic roles
-func basicRoleBindingsCollector(store db.DB) legacyTupleCollector {
- return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) {
- query := `
- SELECT
- ou.org_id, u.uid as user_uid,
- u.is_service_account as is_service_account,
- ou.role as org_role
- FROM org_user ou
- LEFT JOIN ` + store.GetDialect().Quote("user") + ` u ON u.id = ou.user_id
- WHERE ou.org_id = ?
- `
- // FIXME: handle service admin role
- type Binding struct {
- UserUID string `xorm:"user_uid"`
- IsServiceAccount bool `xorm:"is_service_account"`
- OrgRole string `xorm:"org_role"`
- }
-
- var bindings []Binding
- err := store.WithDbSession(ctx, func(sess *db.Session) error {
- return sess.SQL(query, orgID).Find(&bindings)
- })
-
- if err != nil {
- return nil, err
- }
-
- tuples := make(map[string]map[string]*openfgav1.TupleKey)
-
- for _, b := range bindings {
- userType := zanzana.TypeUser
- if b.IsServiceAccount {
- userType = zanzana.TypeServiceAccount
- }
-
- tuple := &openfgav1.TupleKey{
- User: zanzana.NewTupleEntry(userType, b.UserUID, ""),
- Relation: zanzana.RelationAssignee,
- Object: zanzana.NewTupleEntry(zanzana.TypeRole, zanzana.TranslateBasicRole(b.OrgRole), ""),
- }
-
- if tuples[tuple.Object] == nil {
- tuples[tuple.Object] = make(map[string]*openfgav1.TupleKey)
- }
-
- tuples[tuple.Object][tuple.String()] = tuple
- }
-
- return tuples, nil
- }
-}
-
-func teamRoleBindingsCollector(store db.DB) legacyTupleCollector {
- return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) {
- query := `
- SELECT t.uid AS team_uid, r.uid AS role_uid
- FROM team_role tr
- INNER JOIN team t ON tr.team_id = t.id
- INNER JOIN role r ON tr.role_id = r.id
- WHERE t.org_id = ?
- AND r.name NOT LIKE 'managed:%'
- `
- type Binding struct {
- TeamUID string `xorm:"team_uid"`
- RoleUID string `xorm:"role_uid"`
- }
-
- var bindings []Binding
- err := store.WithDbSession(ctx, func(sess *db.Session) error {
- return sess.SQL(query, orgID).Find(&bindings)
- })
-
- if err != nil {
- return nil, err
- }
-
- tuples := make(map[string]map[string]*openfgav1.TupleKey)
-
- for _, b := range bindings {
- tuple := &openfgav1.TupleKey{
- User: zanzana.NewTupleEntry(zanzana.TypeTeam, b.TeamUID, zanzana.RelationTeamMember),
- Relation: zanzana.RelationAssignee,
- Object: zanzana.NewTupleEntry(zanzana.TypeRole, b.RoleUID, ""),
- }
-
- if tuples[tuple.Object] == nil {
- tuples[tuple.Object] = make(map[string]*openfgav1.TupleKey)
- }
-
- tuples[tuple.Object][tuple.String()] = tuple
- }
-
- return tuples, nil
- }
-}
-
-func userRoleBindingsCollector(store db.DB) legacyTupleCollector {
- return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) {
- query := `
- SELECT u.uid AS user_uid, u.is_service_account as is_service_account, r.uid AS role_uid
- FROM user_role ur
- INNER JOIN ` + store.GetDialect().Quote("user") + ` u ON ur.user_id = u.id
- INNER JOIN role r ON ur.role_id = r.id
- WHERE (ur.org_id = 0 OR ur.org_id = ?)
- AND r.name NOT LIKE 'managed:%'
- `
- type Binding struct {
- UserUID string `xorm:"user_uid"`
- IsServiceAccount bool `xorm:"is_service_account"`
- RoleUID string `xorm:"role_uid"`
- }
-
- var bindings []Binding
- err := store.WithDbSession(ctx, func(sess *db.Session) error {
- return sess.SQL(query, orgID).Find(&bindings)
- })
-
- if err != nil {
- return nil, err
- }
-
- tuples := make(map[string]map[string]*openfgav1.TupleKey)
-
- for _, b := range bindings {
- userType := zanzana.TypeUser
- if b.IsServiceAccount {
- userType = zanzana.TypeServiceAccount
- }
-
- tuple := &openfgav1.TupleKey{
- User: zanzana.NewTupleEntry(userType, b.UserUID, ""),
- Relation: zanzana.RelationAssignee,
- Object: zanzana.NewTupleEntry(zanzana.TypeRole, b.RoleUID, ""),
- }
-
- if tuples[tuple.Object] == nil {
- tuples[tuple.Object] = make(map[string]*openfgav1.TupleKey)
- }
-
- tuples[tuple.Object][tuple.String()] = tuple
- }
-
- return tuples, nil
- }
-}
-
-func rolePermissionsCollector(store db.DB) legacyTupleCollector {
- return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) {
- var query = `
- SELECT r.uid as role_uid, p.action, p.kind, p.identifier
- FROM permission p
- INNER JOIN role r ON p.role_id = r.id
- LEFT JOIN builtin_role br ON r.id = br.role_id
- WHERE (r.org_id = 0 OR r.org_id = ?)
- AND r.name NOT LIKE 'managed:%'
- AND r.name NOT LIKE 'fixed:%'
- `
-
- type Permission struct {
- Action string `xorm:"action"`
- Kind string
- Identifier string
- RoleUID string `xorm:"role_uid"`
- }
-
- var permissions []Permission
- err := store.WithDbSession(ctx, func(sess *db.Session) error {
- return sess.SQL(query, orgID).Find(&permissions)
- })
- if err != nil {
- return nil, err
- }
-
- tuples := make(map[string]map[string]*openfgav1.TupleKey)
-
- for _, p := range permissions {
- tuple, ok := zanzana.TranslateToResourceTuple(
- zanzana.NewTupleEntry(zanzana.TypeRole, p.RoleUID, zanzana.RelationAssignee),
- p.Action,
- p.Kind,
- p.Identifier,
- )
- if !ok {
- continue
- }
-
- if tuples[tuple.Object] == nil {
- tuples[tuple.Object] = make(map[string]*openfgav1.TupleKey)
- }
-
- // For resource actions on folders we need to merge the tuples into one with combined
- // group_resources.
- if zanzana.IsFolderResourceTuple(tuple) {
- key := tupleStringWithoutCondition(tuple)
- if t, ok := tuples[tuple.Object][key]; ok {
- zanzana.MergeFolderResourceTuples(t, tuple)
- } else {
- tuples[tuple.Object][key] = tuple
- }
-
- continue
- }
-
- tuples[tuple.Object][tuple.String()] = tuple
- }
-
- return tuples, nil
- }
-}
-
-func fixedRolePermissionsCollector(store db.DB) legacyTupleCollector {
- return func(ctx context.Context, _ int64) (map[string]map[string]*openfgav1.TupleKey, error) {
- var query = `
- SELECT r.uid as role_uid, p.action, p.kind, p.identifier
- FROM permission p
- INNER JOIN role r ON p.role_id = r.id
- LEFT JOIN builtin_role br ON r.id = br.role_id
- WHERE r.org_id = 0
- AND r.name LIKE 'fixed:%'
- `
-
- type Permission struct {
- Action string `xorm:"action"`
- Kind string
- Identifier string
- RoleUID string `xorm:"role_uid"`
- }
-
- var permissions []Permission
- err := store.WithDbSession(ctx, func(sess *db.Session) error {
- return sess.SQL(query).Find(&permissions)
- })
- if err != nil {
- return nil, err
- }
-
- tuples := make(map[string]map[string]*openfgav1.TupleKey)
-
- for _, p := range permissions {
- tuple, ok := zanzana.TranslateToResourceTuple(
- zanzana.NewTupleEntry(zanzana.TypeRole, p.RoleUID, zanzana.RelationAssignee),
- p.Action,
- p.Kind,
- p.Identifier,
- )
- if !ok {
- continue
- }
-
- if tuples[tuple.Object] == nil {
- tuples[tuple.Object] = make(map[string]*openfgav1.TupleKey)
- }
-
- // For resource actions on folders we need to merge the tuples into one with combined
- // group_resources.
- if zanzana.IsFolderResourceTuple(tuple) {
- key := tupleStringWithoutCondition(tuple)
- if t, ok := tuples[tuple.Object][key]; ok {
- zanzana.MergeFolderResourceTuples(t, tuple)
- } else {
- tuples[tuple.Object][key] = tuple
- }
- continue
- }
-
- tuples[tuple.Object][tuple.String()] = tuple
- }
-
- return tuples, nil
- }
-}
-
-// basicRoleBindingsCollector collects role bindings for basic roles
-func anonymousRoleBindingsCollector(cfg *setting.Cfg, store db.DB) legacyTupleCollector {
- return func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error) {
- tuples := make(map[string]map[string]*openfgav1.TupleKey)
- object := zanzana.NewTupleEntry(zanzana.TypeRole, zanzana.TranslateBasicRole(cfg.Anonymous.OrgRole), "")
- // Object should be set to delete obsolete permissions
- tuples[object] = make(map[string]*openfgav1.TupleKey)
-
- o, err := getOrgByName(ctx, store, cfg.Anonymous.OrgName)
- if err != nil {
- return tuples, nil
- }
-
- if o.ID != orgID {
- return tuples, nil
- }
-
- tuple := &openfgav1.TupleKey{
- User: zanzana.NewTupleEntry(zanzana.TypeAnonymous, "0", ""),
- Relation: zanzana.RelationAssignee,
- Object: object,
- }
-
- tuples[tuple.Object][tuple.String()] = tuple
-
- return tuples, nil
- }
-}
-
-func zanzanaCollector(relations []string) zanzanaTupleCollector {
- return func(ctx context.Context, client zanzana.Client, object string, namespace string) (map[string]*openfgav1.TupleKey, error) {
- // list will use continuation token to collect all tuples for object and relation
- list := func(relation string) ([]*openfgav1.Tuple, error) {
- first, err := client.Read(ctx, &authzextv1.ReadRequest{
- Namespace: namespace,
- TupleKey: &authzextv1.ReadRequestTupleKey{
- Object: object,
- Relation: relation,
- },
- })
-
- if err != nil {
- return nil, err
- }
-
- c := first.ContinuationToken
-
- for c != "" {
- res, err := client.Read(ctx, &authzextv1.ReadRequest{
- ContinuationToken: c,
- Namespace: namespace,
- TupleKey: &authzextv1.ReadRequestTupleKey{
- Object: object,
- Relation: relation,
- },
- })
- if err != nil {
- return nil, err
- }
-
- c = res.ContinuationToken
- first.Tuples = append(first.Tuples, res.Tuples...)
- }
-
- return zanzana.ToOpenFGATuples(first.Tuples), nil
- }
-
- out := make(map[string]*openfgav1.TupleKey)
- for _, r := range relations {
- tuples, err := list(r)
- if err != nil {
- return nil, err
- }
- for _, t := range tuples {
- if zanzana.IsFolderResourceTuple(t.Key) {
- out[tupleStringWithoutCondition(t.Key)] = t.Key
- } else {
- out[t.Key.String()] = t.Key
- }
- }
- }
-
- return out, nil
- }
-}
diff --git a/pkg/services/accesscontrol/dualwrite/reconciler.go b/pkg/services/accesscontrol/dualwrite/reconciler.go
deleted file mode 100644
index 7bca9dc1903..00000000000
--- a/pkg/services/accesscontrol/dualwrite/reconciler.go
+++ /dev/null
@@ -1,231 +0,0 @@
-package dualwrite
-
-import (
- "context"
- "fmt"
- "strconv"
- "time"
-
- "go.opentelemetry.io/otel"
-
- claims "github.com/grafana/authlib/types"
-
- "github.com/grafana/grafana/pkg/infra/db"
- "github.com/grafana/grafana/pkg/infra/log"
- "github.com/grafana/grafana/pkg/infra/serverlock"
- "github.com/grafana/grafana/pkg/services/authz/zanzana"
- "github.com/grafana/grafana/pkg/services/featuremgmt"
- "github.com/grafana/grafana/pkg/services/folder"
- "github.com/grafana/grafana/pkg/services/org"
- "github.com/grafana/grafana/pkg/setting"
-)
-
-var tracer = otel.Tracer("github.com/grafana/grafana/pkg/accesscontrol/migrator")
-
-// ZanzanaReconciler is a component to reconcile RBAC permissions to zanzana.
-// We should rewrite the migration after we have "migrated" all possible actions
-// into our schema.
-type ZanzanaReconciler struct {
- cfg *setting.Cfg
- log log.Logger
- features featuremgmt.FeatureToggles
- store db.DB
- client zanzana.Client
- lock *serverlock.ServerLockService
- // reconcilers are migrations that tries to reconcile the state of grafana db to zanzana store.
- // These are run periodically to try to maintain a consistent state.
- reconcilers []resourceReconciler
-}
-
-func ProvideZanzanaReconciler(cfg *setting.Cfg, features featuremgmt.FeatureToggles, client zanzana.Client, store db.DB, lock *serverlock.ServerLockService, folderService folder.Service) *ZanzanaReconciler {
- zanzanaReconciler := &ZanzanaReconciler{
- cfg: cfg,
- log: log.New("zanzana.reconciler"),
- features: features,
- client: client,
- lock: lock,
- store: store,
- reconcilers: []resourceReconciler{
- newResourceReconciler(
- "team memberships",
- teamMembershipCollector(store),
- zanzanaCollector([]string{zanzana.RelationTeamMember, zanzana.RelationTeamAdmin}),
- client,
- ),
- newResourceReconciler(
- "folder tree",
- folderTreeCollector(folderService),
- zanzanaCollector([]string{zanzana.RelationParent}),
- client,
- ),
- newResourceReconciler(
- "managed folder permissions",
- managedPermissionsCollector(store, zanzana.KindFolders),
- zanzanaCollector(zanzana.RelationsFolder),
- client,
- ),
- newResourceReconciler(
- "managed dashboard permissions",
- managedPermissionsCollector(store, zanzana.KindDashboards),
- zanzanaCollector(zanzana.RelationsResouce),
- client,
- ),
- newResourceReconciler(
- "role permissions",
- rolePermissionsCollector(store),
- zanzanaCollector(zanzana.RelationsFolder),
- client,
- ),
- newResourceReconciler(
- "basic role bindings",
- basicRoleBindingsCollector(store),
- zanzanaCollector([]string{zanzana.RelationAssignee}),
- client,
- ),
- newResourceReconciler(
- "team role bindings",
- teamRoleBindingsCollector(store),
- zanzanaCollector([]string{zanzana.RelationAssignee}),
- client,
- ),
- newResourceReconciler(
- "user role bindings",
- userRoleBindingsCollector(store),
- zanzanaCollector([]string{zanzana.RelationAssignee}),
- client,
- ),
- newResourceReconciler(
- "fixed role pemissions",
- fixedRolePermissionsCollector(store),
- zanzanaCollector(zanzana.RelationsFolder),
- client,
- ),
- },
- }
-
- if cfg.Anonymous.Enabled {
- zanzanaReconciler.reconcilers = append(zanzanaReconciler.reconcilers,
- newResourceReconciler(
- "anonymous role binding",
- anonymousRoleBindingsCollector(cfg, store),
- zanzanaCollector([]string{zanzana.RelationAssignee}),
- client,
- ),
- )
- }
-
- return zanzanaReconciler
-}
-
-// Run implements registry.BackgroundService
-func (r *ZanzanaReconciler) Run(ctx context.Context) error {
- if r.features.IsEnabledGlobally(featuremgmt.FlagZanzana) {
- return r.Reconcile(ctx)
- }
- return nil
-}
-
-// Reconcile schedules as job that will run and reconcile resources between
-// legacy access control and zanzana.
-func (r *ZanzanaReconciler) Reconcile(ctx context.Context) error {
- r.reconcile(ctx)
-
- // FIXME:
- // We should be a bit graceful about reconciliations so we are not hammering dbs
- ticker := time.NewTicker(r.cfg.RBAC.ZanzanaReconciliationInterval)
- for {
- select {
- case <-ticker.C:
- r.reconcile(ctx)
- case <-ctx.Done():
- return ctx.Err()
- }
- }
-}
-
-func (r *ZanzanaReconciler) reconcile(ctx context.Context) {
- run := func(ctx context.Context, namespace string) {
- now := time.Now()
- r.log.Debug("Started reconciliation")
-
- for _, reconciler := range r.reconcilers {
- r.log.Debug("Performing zanzana reconciliation", "reconciler", reconciler.name)
- if err := reconciler.reconcile(ctx, namespace); err != nil {
- r.log.Warn("Failed to perform reconciliation for resource", "err", err)
- }
- }
- r.log.Debug("Finished reconciliation", "elapsed", time.Since(now))
- }
-
- var namespaces []string
- if r.cfg.StackID != "" {
- id, err := strconv.ParseInt(r.cfg.StackID, 10, 64)
- if err != nil {
- r.log.Error("cannot perform reconciliation, malformed stack id", "id", r.cfg.StackID, "err", err)
- return
- }
-
- namespaces = []string{claims.CloudNamespaceFormatter(id)}
- } else {
- ids, err := r.getOrgs(ctx)
- if err != nil {
- r.log.Error("cannot perform reconciliation, failed to fetch orgs", "err", err)
- return
- }
-
- for _, id := range ids {
- namespaces = append(namespaces, claims.OrgNamespaceFormatter(id))
- }
- }
-
- if r.lock == nil {
- for _, ns := range namespaces {
- run(ctx, ns)
- }
- return
- }
-
- // We ignore the error for now
- err := r.lock.LockExecuteAndRelease(ctx, "zanzana-reconciliation", 10*time.Hour, func(ctx context.Context) {
- for _, ns := range namespaces {
- run(ctx, ns)
- }
- })
- if err != nil {
- r.log.Error("Error performing zanzana reconciliation", "error", err)
- }
-}
-
-func (r *ZanzanaReconciler) getOrgs(ctx context.Context) ([]int64, error) {
- orgs := make([]int64, 0)
- err := r.store.WithDbSession(ctx, func(sess *db.Session) error {
- q := "SELECT id FROM org"
- if err := sess.SQL(q).Find(&orgs); err != nil {
- return err
- }
- return nil
- })
- if err != nil {
- return nil, err
- }
- return orgs, nil
-}
-
-func getOrgByName(ctx context.Context, store db.DB, name string) (*org.Org, error) {
- var orga org.Org
- err := store.WithDbSession(ctx, func(dbSession *db.Session) error {
- exists, err := dbSession.Where("name=?", name).Get(&orga)
- if err != nil {
- return err
- }
- if !exists {
- return fmt.Errorf("org does not exist: %s", name)
- }
- return nil
- })
-
- if err != nil {
- return nil, err
- }
- return &orga, nil
-}
diff --git a/pkg/services/accesscontrol/dualwrite/resource_reconciler.go b/pkg/services/accesscontrol/dualwrite/resource_reconciler.go
deleted file mode 100644
index af505837569..00000000000
--- a/pkg/services/accesscontrol/dualwrite/resource_reconciler.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package dualwrite
-
-import (
- "context"
- "fmt"
-
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
-
- claims "github.com/grafana/authlib/types"
-
- authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
- "github.com/grafana/grafana/pkg/services/authz/zanzana"
-)
-
-// legacyTupleCollector collects tuples groupd by object and tupleKey
-type legacyTupleCollector func(ctx context.Context, orgID int64) (map[string]map[string]*openfgav1.TupleKey, error)
-
-// zanzanaTupleCollector collects tuples from zanzana for given object
-type zanzanaTupleCollector func(ctx context.Context, client zanzana.Client, object string, namespace string) (map[string]*openfgav1.TupleKey, error)
-
-type resourceReconciler struct {
- name string
- legacy legacyTupleCollector
- zanzana zanzanaTupleCollector
- client zanzana.Client
-}
-
-func newResourceReconciler(name string, legacy legacyTupleCollector, zanzana zanzanaTupleCollector, client zanzana.Client) resourceReconciler {
- return resourceReconciler{name, legacy, zanzana, client}
-}
-
-func (r resourceReconciler) reconcile(ctx context.Context, namespace string) error {
- info, err := claims.ParseNamespace(namespace)
- if err != nil {
- return err
- }
-
- // 1. Fetch grafana resources stored in grafana db.
- res, err := r.legacy(ctx, info.OrgID)
- if err != nil {
- return fmt.Errorf("failed to collect legacy tuples for %s: %w", r.name, err)
- }
-
- var (
- writes = []*openfgav1.TupleKey{}
- deletes = []*openfgav1.TupleKeyWithoutCondition{}
- )
-
- for object, tuples := range res {
- // 2. Fetch all tuples for given object.
- // Due to limitations in open fga api we need to collect tuples per object
- zanzanaTuples, err := r.zanzana(ctx, r.client, object, namespace)
- if err != nil {
- return fmt.Errorf("failed to collect zanzanaa tuples for %s: %w", r.name, err)
- }
-
- // 3. Check if tuples from grafana db exists in zanzana and if not add them to writes
- for key, t := range tuples {
- stored, ok := zanzanaTuples[key]
- if !ok {
- writes = append(writes, t)
- continue
- }
-
- // 4. For folder resource tuples we also need to compare the stored group_resources
- if zanzana.IsFolderResourceTuple(t) && t.String() != stored.String() {
- deletes = append(deletes, &openfgav1.TupleKeyWithoutCondition{
- User: t.User,
- Relation: t.Relation,
- Object: t.Object,
- })
-
- writes = append(writes, t)
- }
- }
-
- // 5. Check if tuple from zanzana don't exists in grafana db, if not add them to deletes.
- for key, tuple := range zanzanaTuples {
- _, ok := tuples[key]
- if !ok {
- deletes = append(deletes, &openfgav1.TupleKeyWithoutCondition{
- User: tuple.User,
- Relation: tuple.Relation,
- Object: tuple.Object,
- })
- }
- }
- }
-
- if len(writes) == 0 && len(deletes) == 0 {
- return nil
- }
-
- if len(deletes) > 0 {
- err := batch(deletes, 100, func(items []*openfgav1.TupleKeyWithoutCondition) error {
- return r.client.Write(ctx, &authzextv1.WriteRequest{
- Namespace: namespace,
- Deletes: &authzextv1.WriteRequestDeletes{TupleKeys: zanzana.ToAuthzExtTupleKeysWithoutCondition(items)},
- })
- })
-
- if err != nil {
- return err
- }
- }
-
- if len(writes) > 0 {
- err := batch(writes, 100, func(items []*openfgav1.TupleKey) error {
- return r.client.Write(ctx, &authzextv1.WriteRequest{
- Namespace: namespace,
- Writes: &authzextv1.WriteRequestWrites{TupleKeys: zanzana.ToAuthzExtTupleKeys(items)},
- })
- })
-
- if err != nil {
- return err
- }
- }
-
- return nil
-}
diff --git a/pkg/services/authz/wireset.go b/pkg/services/authz/wireset.go
index 4a46f15ea3b..f16235fa4b4 100644
--- a/pkg/services/authz/wireset.go
+++ b/pkg/services/authz/wireset.go
@@ -6,5 +6,4 @@ import (
var WireSet = wire.NewSet(
ProvideAuthZClient,
- ProvideZanzana,
)
diff --git a/pkg/services/authz/zanzana.go b/pkg/services/authz/zanzana.go
deleted file mode 100644
index 6067a232082..00000000000
--- a/pkg/services/authz/zanzana.go
+++ /dev/null
@@ -1,233 +0,0 @@
-package authz
-
-import (
- "context"
- "errors"
- "fmt"
-
- "github.com/fullstorydev/grpchan/inprocgrpc"
- grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
- "github.com/prometheus/client_golang/prometheus"
- "google.golang.org/grpc"
- "google.golang.org/grpc/credentials/insecure"
- healthv1pb "google.golang.org/grpc/health/grpc_health_v1"
-
- authnlib "github.com/grafana/authlib/authn"
- authzv1 "github.com/grafana/authlib/authz/proto/v1"
- "github.com/grafana/authlib/types"
- "github.com/grafana/dskit/services"
-
- "github.com/grafana/grafana/pkg/infra/db"
- "github.com/grafana/grafana/pkg/infra/log"
- "github.com/grafana/grafana/pkg/infra/tracing"
- "github.com/grafana/grafana/pkg/services/authn/grpcutils"
- authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
- "github.com/grafana/grafana/pkg/services/authz/zanzana"
- "github.com/grafana/grafana/pkg/services/featuremgmt"
- "github.com/grafana/grafana/pkg/services/grpcserver"
- "github.com/grafana/grafana/pkg/setting"
-)
-
-// ProvideZanzana used to register ZanzanaClient.
-// It will also start an embedded ZanzanaSever if mode is set to "embedded".
-func ProvideZanzana(cfg *setting.Cfg, db db.DB, tracer tracing.Tracer, features featuremgmt.FeatureToggles) (zanzana.Client, error) {
- if !features.IsEnabledGlobally(featuremgmt.FlagZanzana) {
- return zanzana.NewNoopClient(), nil
- }
-
- logger := log.New("zanzana")
-
- var client zanzana.Client
- switch cfg.ZanzanaClient.Mode {
- case setting.ZanzanaModeClient:
- tokenClient, err := authnlib.NewTokenExchangeClient(authnlib.TokenExchangeConfig{
- Token: cfg.ZanzanaClient.Token,
- TokenExchangeURL: cfg.ZanzanaClient.TokenExchangeURL,
- })
- if err != nil {
- return nil, fmt.Errorf("failed to initialize token exchange client: %w", err)
- }
-
- if cfg.StackID == "" {
- return nil, fmt.Errorf("missing stack ID")
- }
-
- dialOptions := []grpc.DialOption{
- // TODO: add TLS support
- grpc.WithTransportCredentials(insecure.NewCredentials()),
- grpc.WithPerRPCCredentials(
- NewGRPCTokenAuth(AuthzServiceAudience, fmt.Sprintf("stacks-%s", cfg.StackID), tokenClient),
- ),
- }
-
- conn, err := grpc.NewClient(cfg.ZanzanaClient.Addr, dialOptions...)
- if err != nil {
- return nil, fmt.Errorf("failed to create zanzana client to remote server: %w", err)
- }
-
- client, err = zanzana.NewClient(conn)
- if err != nil {
- return nil, fmt.Errorf("failed to initialize zanzana client: %w", err)
- }
- case setting.ZanzanaModeEmbedded:
- store, err := zanzana.NewEmbeddedStore(cfg, db, logger)
- if err != nil {
- return nil, fmt.Errorf("failed to start zanzana: %w", err)
- }
-
- openfga, err := zanzana.NewOpenFGAServer(cfg.ZanzanaServer, store, logger)
- if err != nil {
- return nil, fmt.Errorf("failed to start zanzana: %w", err)
- }
-
- srv, err := zanzana.NewServer(cfg.ZanzanaServer, openfga, logger, tracer)
- if err != nil {
- return nil, fmt.Errorf("failed to start zanzana: %w", err)
- }
-
- channel := &inprocgrpc.Channel{}
- // Put * as a namespace so we can properly authorize request with in-proc mode
- channel.WithServerUnaryInterceptor(grpcAuth.UnaryServerInterceptor(func(ctx context.Context) (context.Context, error) {
- ctx = types.WithAuthInfo(ctx, authnlib.NewAccessTokenAuthInfo(authnlib.Claims[authnlib.AccessTokenClaims]{
- Rest: authnlib.AccessTokenClaims{
- Namespace: "*",
- },
- }))
- return ctx, nil
- }))
-
- openfgav1.RegisterOpenFGAServiceServer(channel, openfga)
- authzv1.RegisterAuthzServiceServer(channel, srv)
- authzextv1.RegisterAuthzExtentionServiceServer(channel, srv)
-
- client, err = zanzana.NewClient(channel)
- if err != nil {
- return nil, fmt.Errorf("failed to initialize zanzana client: %w", err)
- }
-
- default:
- return nil, fmt.Errorf("unsupported zanzana mode: %s", cfg.ZanzanaClient.Mode)
- }
-
- return client, nil
-}
-
-type ZanzanaService interface {
- services.NamedService
-}
-
-var _ ZanzanaService = (*Zanzana)(nil)
-
-// ProvideZanzanaService is used to register zanzana as a module so we can run it seperatly from grafana.
-func ProvideZanzanaService(cfg *setting.Cfg, features featuremgmt.FeatureToggles) (*Zanzana, error) {
- s := &Zanzana{
- cfg: cfg,
- features: features,
- logger: log.New("zanzana"),
- }
-
- s.BasicService = services.NewBasicService(s.start, s.running, s.stopping).WithName("zanzana")
-
- return s, nil
-}
-
-type Zanzana struct {
- *services.BasicService
-
- cfg *setting.Cfg
-
- logger log.Logger
- handle grpcserver.Provider
- features featuremgmt.FeatureToggles
-}
-
-func (z *Zanzana) start(ctx context.Context) error {
- tracingCfg, err := tracing.ProvideTracingConfig(z.cfg)
- if err != nil {
- return err
- }
-
- tracingCfg.ServiceName = "zanzana"
-
- tracer, err := tracing.ProvideService(tracingCfg)
- if err != nil {
- return err
- }
-
- store, err := zanzana.NewStore(z.cfg, z.logger)
- if err != nil {
- return fmt.Errorf("failed to initilize zanana store: %w", err)
- }
-
- openfgaServer, err := zanzana.NewOpenFGAServer(z.cfg.ZanzanaServer, store, z.logger)
- if err != nil {
- return fmt.Errorf("failed to start zanzana: %w", err)
- }
-
- zanzanaServer, err := zanzana.NewServer(z.cfg.ZanzanaServer, openfgaServer, z.logger, tracer)
- if err != nil {
- return fmt.Errorf("failed to start zanzana: %w", err)
- }
-
- authenticator := authnlib.NewAccessTokenAuthenticator(
- authnlib.NewAccessTokenVerifier(
- authnlib.VerifierConfig{AllowedAudiences: []string{AuthzServiceAudience}},
- authnlib.NewKeyRetriever(authnlib.KeyRetrieverConfig{
- SigningKeysURL: z.cfg.ZanzanaServer.SigningKeysURL,
- }),
- ),
- )
-
- z.handle, err = grpcserver.ProvideService(
- z.cfg,
- z.features,
- grpcutils.NewAuthenticatorInterceptor(authenticator, tracer),
- tracer,
- prometheus.DefaultRegisterer,
- )
- if err != nil {
- return fmt.Errorf("failed to create zanzana grpc server: %w", err)
- }
-
- grpcServer := z.handle.GetServer()
- openfgav1.RegisterOpenFGAServiceServer(grpcServer, openfgaServer)
- authzv1.RegisterAuthzServiceServer(grpcServer, zanzanaServer)
- authzextv1.RegisterAuthzExtentionServiceServer(grpcServer, zanzanaServer)
-
- // register grpc health server
- healthServer := zanzana.NewHealthServer(zanzanaServer)
- healthv1pb.RegisterHealthServer(grpcServer, healthServer)
-
- if _, err := grpcserver.ProvideReflectionService(z.cfg, z.handle); err != nil {
- return fmt.Errorf("failed to register reflection for zanzana: %w", err)
- }
-
- return nil
-}
-
-func (z *Zanzana) running(ctx context.Context) error {
- if z.cfg.Env == setting.Dev && z.cfg.ZanzanaServer.OpenFGAHttpAddr != "" {
- srv, err := zanzana.NewOpenFGAHttpServer(z.cfg.ZanzanaServer, z.handle)
- if err != nil {
- z.logger.Error("failed to create OpenFGA HTTP server", "error", err)
- } else {
- go func() {
- z.logger.Info("Starting OpenFGA HTTP server")
- if err := srv.ListenAndServe(); err != nil {
- z.logger.Error("failed to start OpenFGA HTTP server", "error", err)
- }
- }()
- }
- }
-
- // Run is blocking so we can just run it here
- return z.handle.Run(ctx)
-}
-
-func (z *Zanzana) stopping(err error) error {
- if err != nil && !errors.Is(err, context.Canceled) {
- z.logger.Error("Stopping zanzana due to unexpected error", "err", err)
- }
- return nil
-}
diff --git a/pkg/services/authz/zanzana/client.go b/pkg/services/authz/zanzana/client.go
deleted file mode 100644
index 4973b128285..00000000000
--- a/pkg/services/authz/zanzana/client.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package zanzana
-
-import (
- "context"
-
- "google.golang.org/grpc"
-
- authlib "github.com/grafana/authlib/types"
- authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
- "github.com/grafana/grafana/pkg/services/authz/zanzana/client"
-)
-
-// Client is a wrapper around [openfgav1.OpenFGAServiceClient]
-type Client interface {
- authlib.AccessClient
- Read(ctx context.Context, req *authzextv1.ReadRequest) (*authzextv1.ReadResponse, error)
- Write(ctx context.Context, req *authzextv1.WriteRequest) error
- BatchCheck(ctx context.Context, req *authzextv1.BatchCheckRequest) (*authzextv1.BatchCheckResponse, error)
-}
-
-func NewClient(cc grpc.ClientConnInterface) (*client.Client, error) {
- return client.New(cc)
-}
-
-func NewNoopClient() *client.NoopClient {
- return client.NewNoop()
-}
diff --git a/pkg/services/authz/zanzana/client/client.go b/pkg/services/authz/zanzana/client/client.go
deleted file mode 100644
index 6860f6831be..00000000000
--- a/pkg/services/authz/zanzana/client/client.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package client
-
-import (
- "context"
-
- "go.opentelemetry.io/otel"
- "google.golang.org/grpc"
-
- authzv1 "github.com/grafana/authlib/authz/proto/v1"
- authlib "github.com/grafana/authlib/types"
-
- "github.com/grafana/grafana/pkg/apimachinery/utils"
- "github.com/grafana/grafana/pkg/infra/log"
- authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
-)
-
-var _ authlib.AccessClient = (*Client)(nil)
-
-var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/authz/zanzana/client")
-
-type Client struct {
- logger log.Logger
- authz authzv1.AuthzServiceClient
- authzext authzextv1.AuthzExtentionServiceClient
-}
-
-func New(cc grpc.ClientConnInterface) (*Client, error) {
- c := &Client{
- authz: authzv1.NewAuthzServiceClient(cc),
- authzext: authzextv1.NewAuthzExtentionServiceClient(cc),
- logger: log.New("zanzana-client"),
- }
-
- return c, nil
-}
-
-func (c *Client) Check(ctx context.Context, id authlib.AuthInfo, req authlib.CheckRequest) (authlib.CheckResponse, error) {
- ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Check")
- defer span.End()
-
- res, err := c.authz.Check(ctx, &authzv1.CheckRequest{
- Subject: id.GetUID(),
- Verb: req.Verb,
- Group: req.Group,
- Resource: req.Resource,
- Namespace: req.Namespace,
- Name: req.Name,
- Subresource: req.Subresource,
- Path: req.Path,
- Folder: req.Folder,
- })
-
- if err != nil {
- return authlib.CheckResponse{}, err
- }
-
- return authlib.CheckResponse{Allowed: res.GetAllowed()}, nil
-}
-
-func (c *Client) Compile(ctx context.Context, id authlib.AuthInfo, req authlib.ListRequest) (authlib.ItemChecker, error) {
- ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Compile")
- defer span.End()
-
- res, err := c.authz.List(ctx, &authzv1.ListRequest{
- Subject: id.GetUID(),
- Group: req.Group,
- Verb: utils.VerbList,
- Resource: req.Resource,
- Namespace: req.Namespace,
- })
-
- if err != nil {
- return nil, err
- }
-
- return newItemChecker(res), nil
-}
-
-func newItemChecker(res *authzv1.ListResponse) authlib.ItemChecker {
- // if we can see all resource of this type we can just return a function that always return true
- if res.GetAll() {
- return func(_, _ string) bool { return true }
- }
-
- folders := make(map[string]struct{}, len(res.Folders))
- for _, f := range res.Folders {
- folders[f] = struct{}{}
- }
-
- items := make(map[string]struct{}, len(res.Items))
- for _, i := range res.Items {
- items[i] = struct{}{}
- }
-
- return func(name, folder string) bool {
- if _, ok := items[name]; ok {
- return true
- }
- if _, ok := folders[folder]; ok {
- return true
- }
- return false
- }
-}
-
-func (c *Client) Read(ctx context.Context, req *authzextv1.ReadRequest) (*authzextv1.ReadResponse, error) {
- ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Read")
- defer span.End()
-
- return c.authzext.Read(ctx, req)
-}
-
-func (c *Client) Write(ctx context.Context, req *authzextv1.WriteRequest) error {
- ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Write")
- defer span.End()
-
- _, err := c.authzext.Write(ctx, req)
- return err
-}
-
-func (c *Client) BatchCheck(ctx context.Context, req *authzextv1.BatchCheckRequest) (*authzextv1.BatchCheckResponse, error) {
- ctx, span := tracer.Start(ctx, "authlib.zanzana.client.Check")
- defer span.End()
-
- return c.authzext.BatchCheck(ctx, req)
-}
diff --git a/pkg/services/authz/zanzana/client/noop.go b/pkg/services/authz/zanzana/client/noop.go
deleted file mode 100644
index 5d5d8ebc29d..00000000000
--- a/pkg/services/authz/zanzana/client/noop.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package client
-
-import (
- "context"
-
- authlib "github.com/grafana/authlib/types"
- authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
-)
-
-var _ authlib.AccessClient = (*NoopClient)(nil)
-
-func NewNoop() *NoopClient {
- return &NoopClient{}
-}
-
-type NoopClient struct{}
-
-func (nc *NoopClient) Check(ctx context.Context, id authlib.AuthInfo, req authlib.CheckRequest) (authlib.CheckResponse, error) {
- return authlib.CheckResponse{}, nil
-}
-
-func (nc *NoopClient) Compile(ctx context.Context, id authlib.AuthInfo, req authlib.ListRequest) (authlib.ItemChecker, error) {
- return nil, nil
-}
-
-func (nc NoopClient) Read(ctx context.Context, req *authzextv1.ReadRequest) (*authzextv1.ReadResponse, error) {
- return nil, nil
-}
-
-func (nc NoopClient) Write(ctx context.Context, req *authzextv1.WriteRequest) error {
- return nil
-}
-
-func (nc NoopClient) BatchCheck(ctx context.Context, req *authzextv1.BatchCheckRequest) (*authzextv1.BatchCheckResponse, error) {
- return nil, nil
-}
diff --git a/pkg/services/authz/zanzana/common/info.go b/pkg/services/authz/zanzana/common/info.go
deleted file mode 100644
index 54fb8f2a35a..00000000000
--- a/pkg/services/authz/zanzana/common/info.go
+++ /dev/null
@@ -1,149 +0,0 @@
-package common
-
-import (
- "google.golang.org/protobuf/types/known/structpb"
-
- authzv1 "github.com/grafana/authlib/authz/proto/v1"
- folderalpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
- authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
-)
-
-type typeInfo struct {
- Type string
- Relations []string
-}
-
-var typedResources = map[string]typeInfo{
- FormatGroupResource(
- folderalpha1.FolderResourceInfo.GroupResource().Group,
- folderalpha1.FolderResourceInfo.GroupResource().Resource,
- "",
- ): {Type: "folder", Relations: RelationsFolder},
-}
-
-func getTypeInfo(group, resource string) (typeInfo, bool) {
- info, ok := typedResources[FormatGroupResource(group, resource, "")]
- return info, ok
-}
-
-func NewResourceInfoFromCheck(r *authzv1.CheckRequest) ResourceInfo {
- typ, relations := getTypeAndRelations(r.GetGroup(), r.GetResource())
- return newResource(
- typ,
- r.GetGroup(),
- r.GetResource(),
- r.GetName(),
- r.GetFolder(),
- r.GetSubresource(),
- relations,
- )
-}
-
-func NewResourceInfoFromBatchItem(i *authzextv1.BatchCheckItem) ResourceInfo {
- typ, relations := getTypeAndRelations(i.GetGroup(), i.GetResource())
- return newResource(
- typ,
- i.GetGroup(),
- i.GetResource(),
- i.GetName(),
- i.GetFolder(),
- i.GetSubresource(),
- relations,
- )
-}
-
-func NewResourceInfoFromList(r *authzv1.ListRequest) ResourceInfo {
- typ, relations := getTypeAndRelations(r.GetGroup(), r.GetResource())
- return newResource(
- typ,
- r.GetGroup(),
- r.GetResource(),
- "",
- "",
- r.GetSubresource(),
- relations,
- )
-}
-
-func getTypeAndRelations(group, resource string) (string, []string) {
- if info, ok := getTypeInfo(group, resource); ok {
- return info.Type, info.Relations
- }
- return TypeResource, RelationsResource
-}
-
-func newResource(
- typ, group, resource, name, folder, subresource string, relations []string,
-) ResourceInfo {
- return ResourceInfo{
- typ: typ,
- group: group,
- resource: resource,
- name: name,
- folder: folder,
- subresource: subresource,
- relations: relations,
- }
-}
-
-type ResourceInfo struct {
- typ string
- group string
- resource string
- name string
- folder string
- subresource string
- relations []string
-}
-
-func (r ResourceInfo) GroupResource() string {
- return FormatGroupResource(r.group, r.resource, r.subresource)
-}
-
-func (r ResourceInfo) GroupResourceIdent() string {
- return NewGroupResourceIdent(r.group, r.resource, r.subresource)
-}
-
-func (r ResourceInfo) ResourceIdent() string {
- if r.name == "" {
- return ""
- }
-
- if r.IsGeneric() {
- return NewResourceIdent(r.group, r.resource, r.subresource, r.name)
- }
-
- return NewTypedIdent(r.typ, r.name)
-}
-
-func (r ResourceInfo) FolderIdent() string {
- if r.folder == "" {
- return ""
- }
-
- return NewFolderIdent(r.folder)
-}
-
-func (r ResourceInfo) IsGeneric() bool {
- return r.typ == TypeResource
-}
-
-func (r ResourceInfo) Type() string {
- return r.typ
-}
-
-func (r ResourceInfo) Context() *structpb.Struct {
- if !r.IsGeneric() {
- return nil
- }
-
- return &structpb.Struct{
- Fields: map[string]*structpb.Value{
- "requested_group": structpb.NewStringValue(r.GroupResource()),
- },
- }
-}
-
-func (r ResourceInfo) IsValidRelation(relation string) bool {
- return isValidRelation(relation, r.relations)
-}
diff --git a/pkg/services/authz/zanzana/common/tuple.go b/pkg/services/authz/zanzana/common/tuple.go
deleted file mode 100644
index 1259aadbefa..00000000000
--- a/pkg/services/authz/zanzana/common/tuple.go
+++ /dev/null
@@ -1,360 +0,0 @@
-package common
-
-import (
- "strings"
-
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
- "google.golang.org/protobuf/types/known/structpb"
-
- "github.com/grafana/grafana/pkg/apimachinery/utils"
- dashboardalpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
- authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
-)
-
-const (
- TypeUser string = "user"
- TypeServiceAccount string = "service-account"
- TypeRenderService string = "render"
- TypeAnonymous string = "anonymous"
- TypeTeam string = "team"
- TypeRole string = "role"
-)
-
-const (
- TypeFolder string = "folder"
- TypeResource string = "resource"
- TypeGroupResouce string = "group_resource"
-)
-
-const (
- TypeFolderPrefix string = TypeFolder + ":"
- TypeResourcePrefix string = TypeResource + ":"
- TypeGroupResoucePrefix string = TypeGroupResouce + ":"
-)
-
-const (
- RelationTeamMember string = "member"
- RelationTeamAdmin string = "admin"
- RelationParent string = "parent"
- RelationAssignee string = "assignee"
-
- RelationSetView string = "view"
- RelationSetEdit string = "edit"
- RelationSetAdmin string = "admin"
-
- RelationGet string = "get"
- RelationUpdate string = "update"
- RelationCreate string = "create"
- RelationDelete string = "delete"
-
- RelationGetPermissions string = "get_permissions"
- RelationSetPermissions string = "set_permissions"
-
- RelationFolderResourceSetView string = "resource_" + RelationSetView
- RelationFolderResourceSetEdit string = "resource_" + RelationSetEdit
- RelationFolderResourceSetAdmin string = "resource_" + RelationSetAdmin
-
- RelationFolderResourceGet string = "resource_" + RelationGet
- RelationFolderResourceUpdate string = "resource_" + RelationUpdate
- RelationFolderResourceCreate string = "resource_" + RelationCreate
- RelationFolderResourceDelete string = "resource_" + RelationDelete
- RelationFolderResourceGetPermissions string = "resource_" + RelationGetPermissions
- RelationFolderResourceSetPermissions string = "resource_" + RelationSetPermissions
-)
-
-// RelationsGroupResource are relations that can be added on type "group_resource".
-var RelationsGroupResource = []string{
- RelationGet,
- RelationUpdate,
- RelationCreate,
- RelationDelete,
- RelationGetPermissions,
- RelationSetPermissions,
-}
-
-// RelationsResource are relations that can be added on type "resource".
-var RelationsResource = []string{
- RelationGet,
- RelationUpdate,
- RelationDelete,
- RelationGetPermissions,
- RelationSetPermissions,
-}
-
-// RelationsFolderResource are relations that can be added on type "folder" for child resources.
-var RelationsFolderResource = []string{
- RelationFolderResourceGet,
- RelationFolderResourceUpdate,
- RelationFolderResourceCreate,
- RelationFolderResourceDelete,
- RelationFolderResourceGetPermissions,
- RelationFolderResourceSetPermissions,
-}
-
-// RelationsFolder are relations that can be added on type "folder".
-var RelationsFolder = append(
- RelationsFolderResource,
- RelationGet,
- RelationUpdate,
- RelationCreate,
- RelationDelete,
- RelationGetPermissions,
- RelationSetPermissions,
-)
-
-// VerbMapping is mapping a k8s verb to a zanzana relation.
-var VerbMapping = map[string]string{
- utils.VerbGet: RelationGet,
- utils.VerbList: RelationGet,
- utils.VerbWatch: RelationGet,
- utils.VerbCreate: RelationCreate,
- utils.VerbUpdate: RelationUpdate,
- utils.VerbPatch: RelationUpdate,
- utils.VerbDelete: RelationDelete,
- utils.VerbDeleteCollection: RelationDelete,
- utils.VerbGetPermissions: RelationGetPermissions,
- utils.VerbSetPermissions: RelationSetPermissions,
-}
-
-// RelationToVerbMapping is mapping a zanzana relation to k8s verb.
-var RelationToVerbMapping = map[string]string{
- RelationGet: utils.VerbGet,
- RelationCreate: utils.VerbCreate,
- RelationUpdate: utils.VerbUpdate,
- RelationDelete: utils.VerbDelete,
- RelationGetPermissions: utils.VerbGetPermissions,
- RelationSetPermissions: utils.VerbSetPermissions,
-}
-
-func IsGroupResourceRelation(relation string) bool {
- return isValidRelation(relation, RelationsGroupResource)
-}
-
-func IsFolderResourceRelation(relation string) bool {
- return isValidRelation(relation, RelationsFolderResource)
-}
-
-func isValidRelation(relation string, valid []string) bool {
- for _, r := range valid {
- if r == relation {
- return true
- }
- }
- return false
-}
-
-func FolderResourceRelation(relation string) string {
- return TypeResource + "_" + relation
-}
-
-func NewTypedIdent(typ string, name string) string {
- return typ + ":" + name
-}
-
-func NewResourceIdent(group, resource, subresource, name string) string {
- return TypeResourcePrefix + FormatGroupResource(group, resource, subresource) + "/" + name
-}
-
-func NewFolderIdent(name string) string {
- return TypeFolderPrefix + name
-}
-
-func NewGroupResourceIdent(group, resource, subresource string) string {
- return TypeGroupResoucePrefix + FormatGroupResource(group, resource, subresource)
-}
-
-func FormatGroupResource(group, resource, subresource string) string {
- b := strings.Builder{}
- b.WriteString(group)
- b.WriteRune('/')
- b.WriteString(resource)
-
- if subresource != "" {
- b.WriteRune('/')
- b.WriteString(subresource)
- }
-
- return b.String()
-}
-
-func NewResourceTuple(subject, relation, group, resource, subresource, name string) *openfgav1.TupleKey {
- return &openfgav1.TupleKey{
- User: subject,
- Relation: relation,
- Object: NewResourceIdent(group, resource, subresource, name),
- Condition: &openfgav1.RelationshipCondition{
- Name: "group_filter",
- Context: &structpb.Struct{
- Fields: map[string]*structpb.Value{
- "group_resource": structpb.NewStringValue(FormatGroupResource(group, resource, subresource)),
- },
- },
- },
- }
-}
-
-func isFolderResourceRelationSet(relation string) bool {
- return relation == RelationFolderResourceSetView ||
- relation == RelationFolderResourceSetEdit ||
- relation == RelationFolderResourceSetAdmin
-}
-
-func NewFolderResourceTuple(subject, relation, group, resource, subresource, folder string) *openfgav1.TupleKey {
- relation = FolderResourceRelation(relation)
- var condition *openfgav1.RelationshipCondition
- if !isFolderResourceRelationSet(relation) {
- condition = &openfgav1.RelationshipCondition{
- Name: "folder_group_filter",
- Context: &structpb.Struct{
- Fields: map[string]*structpb.Value{
- "group_resources": structpb.NewListValue(&structpb.ListValue{
- Values: []*structpb.Value{structpb.NewStringValue(FormatGroupResource(group, resource, subresource))},
- }),
- },
- },
- }
- }
-
- return &openfgav1.TupleKey{
- User: subject,
- Relation: relation,
- Object: NewFolderIdent(folder),
- Condition: condition,
- }
-}
-
-func NewGroupResourceTuple(subject, relation, group, resource, subresource string) *openfgav1.TupleKey {
- return &openfgav1.TupleKey{
- User: subject,
- Relation: relation,
- Object: NewGroupResourceIdent(group, resource, subresource),
- }
-}
-
-func NewFolderParentTuple(folder, parent string) *openfgav1.TupleKey {
- return &openfgav1.TupleKey{
- Object: NewFolderIdent(folder),
- Relation: RelationParent,
- User: NewFolderIdent(parent),
- }
-}
-
-func NewFolderTuple(subject, relation, name string) *openfgav1.TupleKey {
- return NewTypedTuple(TypeFolder, subject, relation, name)
-}
-
-func NewTypedTuple(typ, subject, relation, name string) *openfgav1.TupleKey {
- return &openfgav1.TupleKey{
- User: subject,
- Relation: relation,
- Object: NewTypedIdent(typ, name),
- }
-}
-
-func ToAuthzExtTupleKey(t *openfgav1.TupleKey) *authzextv1.TupleKey {
- tupleKey := &authzextv1.TupleKey{
- User: t.GetUser(),
- Relation: t.GetRelation(),
- Object: t.GetObject(),
- }
-
- if t.GetCondition() != nil {
- tupleKey.Condition = &authzextv1.RelationshipCondition{
- Name: t.GetCondition().GetName(),
- Context: t.GetCondition().GetContext(),
- }
- }
-
- return tupleKey
-}
-
-func ToAuthzExtTupleKeys(tuples []*openfgav1.TupleKey) []*authzextv1.TupleKey {
- result := make([]*authzextv1.TupleKey, 0, len(tuples))
- for _, t := range tuples {
- result = append(result, ToAuthzExtTupleKey(t))
- }
- return result
-}
-
-func ToAuthzExtTupleKeyWithoutCondition(t *openfgav1.TupleKeyWithoutCondition) *authzextv1.TupleKeyWithoutCondition {
- return &authzextv1.TupleKeyWithoutCondition{
- User: t.GetUser(),
- Relation: t.GetRelation(),
- Object: t.GetObject(),
- }
-}
-
-func ToAuthzExtTupleKeysWithoutCondition(tuples []*openfgav1.TupleKeyWithoutCondition) []*authzextv1.TupleKeyWithoutCondition {
- result := make([]*authzextv1.TupleKeyWithoutCondition, 0, len(tuples))
- for _, t := range tuples {
- result = append(result, ToAuthzExtTupleKeyWithoutCondition(t))
- }
- return result
-}
-
-func ToOpenFGATupleKey(t *authzextv1.TupleKey) *openfgav1.TupleKey {
- tupleKey := &openfgav1.TupleKey{
- User: t.GetUser(),
- Relation: t.GetRelation(),
- Object: t.GetObject(),
- }
-
- if t.GetCondition() != nil {
- tupleKey.Condition = &openfgav1.RelationshipCondition{
- Name: t.GetCondition().GetName(),
- Context: t.GetCondition().GetContext(),
- }
- }
-
- return tupleKey
-}
-
-func ToOpenFGATupleKeys(tuples []*authzextv1.TupleKey) []*openfgav1.TupleKey {
- result := make([]*openfgav1.TupleKey, 0, len(tuples))
- for _, t := range tuples {
- result = append(result, ToOpenFGATupleKey(t))
- }
- return result
-}
-
-func ToOpenFGATupleKeyWithoutCondition(t *authzextv1.TupleKeyWithoutCondition) *openfgav1.TupleKeyWithoutCondition {
- return &openfgav1.TupleKeyWithoutCondition{
- User: t.GetUser(),
- Relation: t.GetRelation(),
- Object: t.GetObject(),
- }
-}
-
-func ToOpenFGATuple(t *authzextv1.Tuple) *openfgav1.Tuple {
- return &openfgav1.Tuple{
- Key: ToOpenFGATupleKey(t.GetKey()),
- Timestamp: t.GetTimestamp(),
- }
-}
-
-func ToOpenFGATuples(tuples []*authzextv1.Tuple) []*openfgav1.Tuple {
- result := make([]*openfgav1.Tuple, 0, len(tuples))
- for _, t := range tuples {
- result = append(result, ToOpenFGATuple(t))
- }
- return result
-}
-
-func AddRenderContext(req *openfgav1.CheckRequest) {
- if req.ContextualTuples == nil {
- req.ContextualTuples = &openfgav1.ContextualTupleKeys{}
- }
- if req.ContextualTuples.TupleKeys == nil {
- req.ContextualTuples.TupleKeys = make([]*openfgav1.TupleKey, 0)
- }
-
- req.ContextualTuples.TupleKeys = append(req.ContextualTuples.TupleKeys, &openfgav1.TupleKey{
- User: req.TupleKey.User,
- Relation: RelationSetView,
- Object: NewGroupResourceIdent(
- dashboardalpha1.DashboardResourceInfo.GroupResource().Group,
- dashboardalpha1.DashboardResourceInfo.GroupResource().Resource,
- "",
- ),
- })
-}
diff --git a/pkg/services/authz/zanzana/logger/logger.go b/pkg/services/authz/zanzana/logger/logger.go
deleted file mode 100644
index 1e1f2a6435d..00000000000
--- a/pkg/services/authz/zanzana/logger/logger.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package logger
-
-import (
- "context"
-
- "go.uber.org/zap"
- "go.uber.org/zap/zapcore"
-
- "github.com/grafana/grafana/pkg/infra/log"
- "github.com/openfga/openfga/pkg/logger"
-)
-
-var _ logger.Logger = (*ZanzanaLogger)(nil)
-
-// ZanzanaLogger is a grafana logger wrapper compatible with OpenFGA logger interface
-type ZanzanaLogger struct {
- logger log.Logger
-}
-
-func New(logger log.Logger) *ZanzanaLogger {
- return &ZanzanaLogger{
- logger: logger,
- }
-}
-
-// Simple converter for zap logger fields
-func zapFieldsToArgs(fields []zap.Field) []any {
- // We need to pre-allocated space for key and value
- args := make([]any, 0, len(fields)*2)
- for _, f := range fields {
- args = append(args, f.Key)
- if f.Interface != nil {
- args = append(args, f.Interface)
- } else if f.String != "" {
- args = append(args, f.String)
- } else {
- args = append(args, f.Integer)
- }
- }
- return args
-}
-
-// With implements logger.Logger.
-func (l *ZanzanaLogger) With(fields ...zapcore.Field) logger.Logger {
- return &ZanzanaLogger{
- logger: l.logger.New(zapFieldsToArgs(fields)...),
- }
-}
-
-func (l *ZanzanaLogger) Debug(msg string, fields ...zap.Field) {
- l.logger.Debug(msg, zapFieldsToArgs(fields)...)
-}
-
-func (l *ZanzanaLogger) Info(msg string, fields ...zap.Field) {
- l.logger.Info(msg, zapFieldsToArgs(fields)...)
-}
-
-func (l *ZanzanaLogger) Warn(msg string, fields ...zap.Field) {
- l.logger.Warn(msg, zapFieldsToArgs(fields)...)
-}
-
-func (l *ZanzanaLogger) Error(msg string, fields ...zap.Field) {
- l.logger.Error(msg, zapFieldsToArgs(fields)...)
-}
-
-func (l *ZanzanaLogger) Panic(msg string, fields ...zap.Field) {
- l.logger.Error(msg, zapFieldsToArgs(fields)...)
-}
-
-func (l *ZanzanaLogger) Fatal(msg string, fields ...zap.Field) {
- l.logger.Error(msg, zapFieldsToArgs(fields)...)
-}
-
-func (l *ZanzanaLogger) DebugWithContext(ctx context.Context, msg string, fields ...zap.Field) {
- l.logger.Debug(msg, zapFieldsToArgs(fields)...)
-}
-
-func (l *ZanzanaLogger) InfoWithContext(ctx context.Context, msg string, fields ...zap.Field) {
- l.logger.Info(msg, zapFieldsToArgs(fields)...)
-}
-
-func (l *ZanzanaLogger) WarnWithContext(ctx context.Context, msg string, fields ...zap.Field) {
- l.logger.Warn(msg, zapFieldsToArgs(fields)...)
-}
-
-func (l *ZanzanaLogger) ErrorWithContext(ctx context.Context, msg string, fields ...zap.Field) {
- l.logger.Error(msg, zapFieldsToArgs(fields)...)
-}
-
-func (l *ZanzanaLogger) PanicWithContext(ctx context.Context, msg string, fields ...zap.Field) {
- l.logger.Error(msg, zapFieldsToArgs(fields)...)
-}
-
-func (l *ZanzanaLogger) FatalWithContext(ctx context.Context, msg string, fields ...zap.Field) {
- l.logger.Error(msg, zapFieldsToArgs(fields)...)
-}
diff --git a/pkg/services/authz/zanzana/schema/README.md b/pkg/services/authz/zanzana/schema/README.md
deleted file mode 100644
index 4462e0f18e0..00000000000
--- a/pkg/services/authz/zanzana/schema/README.md
+++ /dev/null
@@ -1,61 +0,0 @@
-# Authorization schema
-
-Here's some notes about [OpenFGA authorization model](https://openfga.dev/docs/modeling/getting-started) (schema) using to model access control in Grafana.
-
-## GroupResource level permissions
-
-A relation to a group_resource object grants access to all objects of the GroupResource.
-They take the form of `{ “user”: “user:1”, relation: “read”, object:”group_resource:dashboard.grafana.app/dashboard” }`. This
-example would grant `user:1` access to all `dashboard.grafana.app/dashboard` in the namespace.
-
-## Folder level permissions
-
-Folders have a type in our schema, this is different from most of our other resources where we use the generic type for
-them. This is because we want to store the folder tree relations.
-
-To grant a user access to a specific folder we store `{ “user”: “user:1”, relation: “read”, object:”folder:<name>” }`
-
-To grant a user access to sub resources of a folder we store ``{ “user”: “user:1”, relation: “resource_read”, object:”folder:<uid>”}` with additional context.
-This context holds all GroupResources in a list e.g. `{ "group_resources": ["dashboard.grafana.app/dashboards", "alerting.grafana.app/rules" ] }`.
-
-## Resource level permissions
-
-Most of our resource should use the generic resource type.
-
-To grant a user direct access to a specific resource we store `{ “user”: “user:1”, relation: “read”, object:”resource:dashboard.grafana.app/dashboard/<name>” }` with additional context.
-This context store the GroupResource. `{ "group_resource": "dashboard.grafana.app/dashboards" }`. This is required so we can filter them out for list requests.
-
-## Managed permissions
-
-In the RBAC model managed permissions stored as a special "managed" role permissions. OpenFGA model allows to assign permissions directly to users, so it produces following tuples:
-
-```text
-user:<user_uid> read folder:<folder_uid>
-```
-
-It's also possible to assign permissions for team members using `#member` relation:
-
-```text
-team:<team_uid>#member read folder:<folder_uid>
-```
-
-## Roles and role assignments
-
-RBAC authorization model grants permissions to users through roles and role assignments. All permissions are linked to roles and then roles granted to users. To model this in OpenFGA we use `role` type.
-
-To understand how RBAC permissions linked to roles, let's take a look at the folder read permission as example:
-
-```text
-type role
- relations
- define assignee: [user, team#member, role#assignee]
-
-type folder
- relations
- define parent: [folder]
-
- define read: [user, team#member, role#assignee] or view or read from parent
-```
-
-According to the schema, user can get `read` access to folder if it has `read` relation granted directly to the folder or its parent folders.
-
diff --git a/pkg/services/authz/zanzana/schema/schema.go b/pkg/services/authz/zanzana/schema/schema.go
deleted file mode 100644
index b4c3b7fcd53..00000000000
--- a/pkg/services/authz/zanzana/schema/schema.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package schema
-
-import (
- _ "embed"
-
- "github.com/openfga/language/pkg/go/transformer"
-)
-
-var (
- //go:embed schema_core.fga
- coreDSL string
- //go:embed schema_folder.fga
- folderDSL string
- //go:embed schema_resource.fga
- resourceDSL string
-)
-
-var SchemaModules = []transformer.ModuleFile{
- {
- Name: "schema_core.fga",
- Contents: coreDSL,
- },
- {
- Name: "schema_folder.fga",
- Contents: folderDSL,
- },
- {
- Name: "schema_resource.fga",
- Contents: resourceDSL,
- },
-}
diff --git a/pkg/services/authz/zanzana/schema/schema_core.fga b/pkg/services/authz/zanzana/schema/schema_core.fga
deleted file mode 100644
index 81a5bf49257..00000000000
--- a/pkg/services/authz/zanzana/schema/schema_core.fga
+++ /dev/null
@@ -1,25 +0,0 @@
-module core
-
-type user
-
-type service-account
-
-type render
-
-type anonymous
-
-type role
- relations
- define assignee: [user, service-account, anonymous, team#member, role#assignee]
-
-type team
- relations
- define admin: [user, service-account]
- define member: [user, service-account] or admin
-
- define get: [user, service-account, team#member, role#assignee] or member
- define update: [user, service-account, team#member, role#assignee] or admin
- define delete: [user, service-account, team#member, role#assignee] or admin
-
- define get_permissions: [user, service-account, team#member, role#assignee] or admin
- define set_permissions: [user, service-account, team#member, role#assignee] or admin
diff --git a/pkg/services/authz/zanzana/schema/schema_folder.fga b/pkg/services/authz/zanzana/schema/schema_folder.fga
deleted file mode 100644
index b9b0a842de9..00000000000
--- a/pkg/services/authz/zanzana/schema/schema_folder.fga
+++ /dev/null
@@ -1,18 +0,0 @@
-module folder
-
-type folder
- relations
- define parent: [folder]
-
- # Action sets
- define view: [user, service-account, team#member, role#assignee] or edit or view from parent
- define edit: [user, service-account, team#member, role#assignee] or admin or edit from parent
- define admin: [user, service-account, team#member, role#assignee] or admin from parent
-
- define get: [user, service-account, team#member, role#assignee] or view or get from parent
- define create: [user, service-account, team#member, role#assignee] or edit or create from parent
- define update: [user, service-account, team#member, role#assignee] or edit or update from parent
- define delete: [user, service-account, team#member, role#assignee] or edit or delete from parent
-
- define get_permissions: [user, service-account, team#member, role#assignee] or admin or get_permissions from parent
- define set_permissions: [user, service-account, team#member, role#assignee] or admin or set_permissions from parent
diff --git a/pkg/services/authz/zanzana/schema/schema_resource.fga b/pkg/services/authz/zanzana/schema/schema_resource.fga
deleted file mode 100644
index b9893b2a997..00000000000
--- a/pkg/services/authz/zanzana/schema/schema_resource.fga
+++ /dev/null
@@ -1,50 +0,0 @@
-module resource
-
-extend type folder
- relations
- define resource_view: [user, service-account, team#member, role#assignee] or resource_edit or resource_view from parent
- define resource_edit: [user, service-account, team#member, role#assignee] or resource_admin or resource_edit from parent
- define resource_admin: [user, service-account, team#member, role#assignee] or resource_admin from parent
-
- define resource_get: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_view or resource_get from parent
- define resource_create: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_create from parent
- define resource_update: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_update from parent
- define resource_delete: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_edit or resource_delete from parent
-
- define resource_get_permissions: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin or resource_get_permissions from parent
- define resource_set_permissions: [user with folder_group_filter, service-account with folder_group_filter, team#member with folder_group_filter, role#assignee with folder_group_filter] or resource_admin or resource_set_permissions from parent
-
-type group_resource
- relations
- define view: [user, service-account, render, team#member, role#assignee] or edit
- define edit: [user, service-account, team#member, role#assignee] or admin
- define admin: [user, service-account, team#member, role#assignee]
-
- define get: [user, service-account, render, team#member, role#assignee] or view
- define create: [user, service-account, team#member, role#assignee] or edit
- define update: [user, service-account, team#member, role#assignee] or edit
- define delete: [user, service-account, team#member, role#assignee] or edit
-
- define get_permissions: [user, service-account, render, team#member, role#assignee] or admin
- define set_permissions: [user, service-account, render, team#member, role#assignee] or admin
-
-type resource
- relations
- define view: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit
- define edit: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or admin
- define admin: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter]
-
- define get: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or view
- define update: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit
- define delete: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or edit
-
- define get_permissions: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or admin
- define set_permissions: [user with group_filter, service-account with group_filter, team#member with group_filter, role#assignee with group_filter] or admin
-
-condition group_filter(requested_group: string, group_resource: string) {
- requested_group == group_resource
-}
-
-condition folder_group_filter(requested_group: string, group_resources: list<string>) {
- requested_group in group_resources
-}
diff --git a/pkg/services/authz/zanzana/schema/transform.go b/pkg/services/authz/zanzana/schema/transform.go
deleted file mode 100644
index 4f36f331360..00000000000
--- a/pkg/services/authz/zanzana/schema/transform.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package schema
-
-import (
- _ "embed"
- "fmt"
-
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
- language "github.com/openfga/language/pkg/go/transformer"
- "google.golang.org/protobuf/encoding/protojson"
-)
-
-func TransformModulesToModel(modules []language.ModuleFile) (*openfgav1.AuthorizationModel, error) {
- parsedAuthModel, err := language.TransformModuleFilesToModel(modules, "1.2")
- if err != nil {
- return nil, fmt.Errorf("failed to transform dsl to model: %w", err)
- }
-
- return parsedAuthModel, nil
-}
-
-func TransformDSLToModel(dsl string) (*openfgav1.AuthorizationModel, error) {
- parsedAuthModel, err := language.TransformDSLToProto(dsl)
- if err != nil {
- return nil, fmt.Errorf("failed to transform dsl to model: %w", err)
- }
-
- return parsedAuthModel, nil
-}
-
-func TransformToDSL(model *openfgav1.AuthorizationModel, opts ...language.TransformOption) (string, error) {
- return language.TransformJSONProtoToDSL(model, opts...)
-}
-
-// EqualModels compares two authorization models.
-// Id is not comparing since model loaded from disk doesn't contain Id.
-func EqualModels(a, b *openfgav1.AuthorizationModel) bool {
- aCopy := openfgav1.AuthorizationModel{
- SchemaVersion: a.SchemaVersion,
- TypeDefinitions: a.TypeDefinitions,
- Conditions: a.Conditions,
- }
- aJSONBytes, err := protojson.Marshal(&aCopy)
- if err != nil {
- return false
- }
-
- bCopy := openfgav1.AuthorizationModel{
- SchemaVersion: b.SchemaVersion,
- TypeDefinitions: b.TypeDefinitions,
- Conditions: b.Conditions,
- }
- bJSONBytes, err := protojson.Marshal(&bCopy)
- if err != nil {
- return false
- }
-
- return string(aJSONBytes) == string(bJSONBytes)
-}
diff --git a/pkg/services/authz/zanzana/schema/transform_test.go b/pkg/services/authz/zanzana/schema/transform_test.go
deleted file mode 100644
index f309d91e963..00000000000
--- a/pkg/services/authz/zanzana/schema/transform_test.go
+++ /dev/null
@@ -1,313 +0,0 @@
-package schema
-
-import (
- "testing"
-
- "github.com/openfga/language/pkg/go/transformer"
- "github.com/stretchr/testify/assert"
-)
-
-func TestEqualModels(t *testing.T) {
- type testCase struct {
- desc string
- a string
- b string
- expected bool
- }
-
- tests := []testCase{
- {
- desc: "should be equal",
- a: `
-model
- schema 1.1
-
-type instance
-
-type user
-
-type org
- relations
- define instance: [instance]
- define member: [user]
- define viewer: [user]
-
-type role
- relations
- define org: [org]
- define instance: [instance]
- define assignee: [user, team#member, role#assignee]
-
-type team
- relations
- define org: [org]
- define admin: [user]
- define member: [user] or org
- `,
- b: `
-model
- schema 1.1
-
-type instance
-
-type user
-
-type org
- relations
- define instance: [instance]
- define member: [user]
- define viewer: [user]
-
-type role
- relations
- define org: [org]
- define instance: [instance]
- define assignee: [user, team#member, role#assignee]
-
-type team
- relations
- define org: [org]
- define admin: [user]
- define member: [user] or org
- `,
- expected: true,
- },
- {
- desc: "should not be equal",
- a: `
-model
- schema 1.1
-
-type instance
-
-type user
-
-type org
- relations
- define instance: [instance]
- define member: [user]
- define viewer: [user]
-
-type role
- relations
- define org: [org]
- define instance: [instance]
- define assignee: [user, team#member, role#assignee]
-
-type team
- relations
- define org: [org]
- define admin: [user]
- define member: [user] or org
- `,
- b: `
-model
- schema 1.1
-
-type instance
-
-type user
-
-type org
- relations
- define instance: [instance]
- define member: [user]
- define viewer: [user]
-
-type role
- relations
- define org: [org]
- define instance: [instance]
- define assignee: [user, team#member, role#assignee]
-`,
- expected: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.desc, func(t *testing.T) {
- modelA, err := transformer.TransformDSLToProto(tt.a)
- assert.NoError(t, err)
-
- modelB, err := transformer.TransformDSLToProto(tt.b)
- assert.NoError(t, err)
-
- assert.Equal(t, tt.expected, EqualModels(modelA, modelB))
- })
- }
-}
-
-func TestModulesEqualModels(t *testing.T) {
- type testCase struct {
- desc string
- a []transformer.ModuleFile
- b []transformer.ModuleFile
- expected bool
- }
-
- tests := []testCase{
- {
- desc: "should be equal",
- a: []transformer.ModuleFile{
- {
- Name: "core.fga",
- Contents: `
-module core
-
-type instance
-
-type user
-
-type org
- relations
- define instance: [instance]
- define member: [user]
- define viewer: [user]
-
-type role
- relations
- define org: [org]
- define instance: [instance]
- define assignee: [user, team#member, role#assignee]
- `,
- },
- {
- Name: "team.fga",
- Contents: `
-module team
-
-type team
- relations
- define org: [org]
- define admin: [user]
- define member: [user] or org
- `,
- },
- },
- b: []transformer.ModuleFile{
- {
- Name: "core.fga",
- Contents: `
-module core
-
-type instance
-
-type user
-
-type org
- relations
- define instance: [instance]
- define member: [user]
- define viewer: [user]
-
-type role
- relations
- define org: [org]
- define instance: [instance]
- define assignee: [user, team#member, role#assignee]
- `,
- },
- {
- Name: "team.fga",
- Contents: `
-module team
-
-type team
- relations
- define org: [org]
- define admin: [user]
- define member: [user] or org
- `,
- },
- },
- expected: true,
- },
- {
- desc: "should not be equal",
- a: []transformer.ModuleFile{
- {
- Name: "core.fga",
- Contents: `
-module core
-
-type instance
-
-type user
-
-type org
- relations
- define instance: [instance]
- define member: [user]
- define viewer: [user]
-
-type role
- relations
- define org: [org]
- define instance: [instance]
- define assignee: [user, team#member, role#assignee]
- `,
- },
- {
- Name: "team.fga",
- Contents: `
-module team
-
-type team
- relations
- define org: [org]
- define admin: [user]
- define member: [user] or org
- `,
- },
- },
- b: []transformer.ModuleFile{
- {
- Name: "core.fga",
- Contents: `
-module core
-
-type instance
-
-type user
-
-type org
- relations
- define instance: [instance]
- define member: [user]
- define viewer: [user]
-
-type role
- relations
- define org: [org]
- define instance: [instance]
- define assignee: [user, team#member, role#assignee]
- `,
- },
- {
- Name: "folder.fga",
- Contents: `
-module folder
-
-type folder
- relations
- define parent: [folder]
- define org: [org]
- `,
- },
- },
- expected: false,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.desc, func(t *testing.T) {
- modelA, err := TransformModulesToModel(tt.a)
- assert.NoError(t, err)
-
- modelB, err := TransformModulesToModel(tt.b)
- assert.NoError(t, err)
-
- assert.Equal(t, tt.expected, EqualModels(modelA, modelB))
- })
- }
-}
diff --git a/pkg/services/authz/zanzana/server.go b/pkg/services/authz/zanzana/server.go
deleted file mode 100644
index 859e5d9a9bc..00000000000
--- a/pkg/services/authz/zanzana/server.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package zanzana
-
-import (
- "net/http"
-
- openfgaserver "github.com/openfga/openfga/pkg/server"
- openfgastorage "github.com/openfga/openfga/pkg/storage"
-
- "github.com/grafana/grafana/pkg/infra/log"
- "github.com/grafana/grafana/pkg/infra/tracing"
- "github.com/grafana/grafana/pkg/services/authz/zanzana/server"
- "github.com/grafana/grafana/pkg/services/grpcserver"
- "github.com/grafana/grafana/pkg/setting"
-)
-
-func NewServer(cfg setting.ZanzanaServerSettings, openfga server.OpenFGAServer, logger log.Logger, tracer tracing.Tracer) (*server.Server, error) {
- return server.NewServer(cfg, openfga, logger, tracer)
-}
-
-func NewHealthServer(target server.DiagnosticServer) *server.HealthServer {
- return server.NewHealthServer(target)
-}
-
-func NewOpenFGAServer(cfg setting.ZanzanaServerSettings, store openfgastorage.OpenFGADatastore, logger log.Logger) (*openfgaserver.Server, error) {
- return server.NewOpenFGAServer(cfg, store, logger)
-}
-
-func NewOpenFGAHttpServer(cfg setting.ZanzanaServerSettings, srv grpcserver.Provider) (*http.Server, error) {
- return server.NewOpenFGAHttpServer(cfg, srv)
-}
diff --git a/pkg/services/authz/zanzana/server/auth.go b/pkg/services/authz/zanzana/server/auth.go
deleted file mode 100644
index ca1fc911328..00000000000
--- a/pkg/services/authz/zanzana/server/auth.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package server
-
-import (
- "context"
-
- "google.golang.org/grpc/codes"
- "google.golang.org/grpc/status"
-
- claims "github.com/grafana/authlib/types"
-)
-
-func authorize(ctx context.Context, namespace string) error {
- c, ok := claims.AuthInfoFrom(ctx)
- if !ok {
- return status.Errorf(codes.Unauthenticated, "unauthenticated")
- }
- if c.GetNamespace() == "" || namespace == "" {
- return status.Errorf(codes.Unauthenticated, "unauthenticated")
- }
- if !claims.NamespaceMatches(c.GetNamespace(), namespace) {
- return status.Errorf(codes.PermissionDenied, "namespace does not match")
- }
- return nil
-}
diff --git a/pkg/services/authz/zanzana/server/health.go b/pkg/services/authz/zanzana/server/health.go
deleted file mode 100644
index 7acea309956..00000000000
--- a/pkg/services/authz/zanzana/server/health.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package server
-
-import (
- "context"
- "errors"
- "time"
-
- grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
- healthv1pb "google.golang.org/grpc/health/grpc_health_v1"
-)
-
-type DiagnosticServer interface {
- IsHealthy(ctx context.Context) (bool, error)
-}
-
-func NewHealthServer(target DiagnosticServer) *HealthServer {
- return &HealthServer{target: target}
-}
-
-type HealthServer struct {
- healthv1pb.UnimplementedHealthServer
- target DiagnosticServer
-}
-
-var _ grpcauth.ServiceAuthFuncOverride = (*HealthServer)(nil)
-
-func (s *HealthServer) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) {
- return ctx, nil
-}
-
-func (s *HealthServer) Check(ctx context.Context, req *healthv1pb.HealthCheckRequest) (*healthv1pb.HealthCheckResponse, error) {
- healthy, err := s.target.IsHealthy(ctx)
- if err != nil || !healthy {
- return &healthv1pb.HealthCheckResponse{Status: healthv1pb.HealthCheckResponse_NOT_SERVING}, err
- }
- return &healthv1pb.HealthCheckResponse{Status: healthv1pb.HealthCheckResponse_SERVING}, nil
-}
-
-func (s *HealthServer) Watch(req *healthv1pb.HealthCheckRequest, stream healthv1pb.Health_WatchServer) error {
- res, err := s.Check(stream.Context(), &healthv1pb.HealthCheckRequest{})
- if err != nil {
- return err
- }
-
- err = stream.Send(res)
- if err != nil {
- return err
- }
-
- prevStatus := res.GetStatus()
-
- ticker := time.NewTicker(10 * time.Second)
- defer ticker.Stop()
- for {
- select {
- case <-ticker.C:
- res, err := s.Check(stream.Context(), &healthv1pb.HealthCheckRequest{})
- if err != nil {
- return err
- }
-
- // if health status has not changed, continue
- if res.GetStatus() == prevStatus {
- continue
- }
-
- prevStatus = res.GetStatus()
- err = stream.Send(res)
- if err != nil {
- return err
- }
-
- case <-stream.Context().Done():
- return errors.New("stream closed, context cancelled")
- }
- }
-}
diff --git a/pkg/services/authz/zanzana/server/openfga_server.go b/pkg/services/authz/zanzana/server/openfga_server.go
deleted file mode 100644
index 2eb854a81c1..00000000000
--- a/pkg/services/authz/zanzana/server/openfga_server.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package server
-
-import (
- "context"
- "fmt"
- "net/http"
- "time"
-
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
- httpmiddleware "github.com/openfga/openfga/pkg/middleware/http"
- "github.com/openfga/openfga/pkg/server"
- serverErrors "github.com/openfga/openfga/pkg/server/errors"
- "github.com/openfga/openfga/pkg/storage"
-
- "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
- "github.com/rs/cors"
- "google.golang.org/grpc"
- "google.golang.org/grpc/credentials/insecure"
- healthv1pb "google.golang.org/grpc/health/grpc_health_v1"
- "google.golang.org/grpc/status"
-
- "github.com/grafana/grafana/pkg/infra/log"
- "github.com/grafana/grafana/pkg/services/grpcserver"
- "github.com/grafana/grafana/pkg/setting"
-
- zlogger "github.com/grafana/grafana/pkg/services/authz/zanzana/logger"
-)
-
-func NewOpenFGAServer(cfg setting.ZanzanaServerSettings, store storage.OpenFGADatastore, logger log.Logger) (*server.Server, error) {
- opts := []server.OpenFGAServiceV1Option{
- server.WithDatastore(store),
- server.WithLogger(zlogger.New(logger)),
- server.WithCheckQueryCacheEnabled(cfg.CheckQueryCache),
- server.WithCheckQueryCacheTTL(cfg.CheckQueryCacheTTL),
- server.WithListObjectsMaxResults(cfg.ListObjectsMaxResults),
- server.WithListObjectsDeadline(cfg.ListObjectsDeadline),
- }
-
- srv, err := server.NewServerWithOpts(opts...)
- if err != nil {
- return nil, err
- }
-
- return srv, nil
-}
-
-func NewOpenFGAHttpServer(cfg setting.ZanzanaServerSettings, srv grpcserver.Provider) (*http.Server, error) {
- dialOpts := []grpc.DialOption{
- grpc.WithTransportCredentials(insecure.NewCredentials()),
- }
-
- addr := srv.GetAddress()
- // Wait until GRPC server is initialized
- ticker := time.NewTicker(100 * time.Millisecond)
- defer ticker.Stop()
- maxRetries := 100
- retries := 0
- for addr == "" && retries < maxRetries {
- <-ticker.C
- addr = srv.GetAddress()
- retries++
- }
- if addr == "" {
- return nil, fmt.Errorf("failed to create HTTP server: GRPC server unavailable")
- }
-
- conn, err := grpc.NewClient(addr, dialOpts...)
- if err != nil {
- return nil, fmt.Errorf("unable to dial GRPC: %w", err)
- }
-
- muxOpts := []runtime.ServeMuxOption{
- runtime.WithForwardResponseOption(httpmiddleware.HTTPResponseModifier),
- runtime.WithErrorHandler(func(c context.Context,
- sr *runtime.ServeMux, mm runtime.Marshaler, w http.ResponseWriter, r *http.Request, e error) {
- intCode := serverErrors.ConvertToEncodedErrorCode(status.Convert(e))
- httpmiddleware.CustomHTTPErrorHandler(c, w, r, serverErrors.NewEncodedError(intCode, e.Error()))
- }),
- runtime.WithStreamErrorHandler(func(ctx context.Context, e error) *status.Status {
- intCode := serverErrors.ConvertToEncodedErrorCode(status.Convert(e))
- encodedErr := serverErrors.NewEncodedError(intCode, e.Error())
- return status.Convert(encodedErr)
- }),
- runtime.WithHealthzEndpoint(healthv1pb.NewHealthClient(conn)),
- runtime.WithOutgoingHeaderMatcher(func(s string) (string, bool) { return s, true }),
- }
- mux := runtime.NewServeMux(muxOpts...)
- if err := openfgav1.RegisterOpenFGAServiceHandler(context.TODO(), mux, conn); err != nil {
- return nil, fmt.Errorf("failed to register gateway handler: %w", err)
- }
-
- return &http.Server{
- Addr: cfg.OpenFGAHttpAddr,
- Handler: cors.New(cors.Options{
- AllowedOrigins: []string{"*"},
- AllowCredentials: true,
- AllowedHeaders: []string{"*"},
- AllowedMethods: []string{http.MethodGet, http.MethodPost,
- http.MethodHead, http.MethodPatch, http.MethodDelete, http.MethodPut},
- }).Handler(mux),
- ReadHeaderTimeout: 30 * time.Second,
- }, nil
-}
diff --git a/pkg/services/authz/zanzana/server/server.go b/pkg/services/authz/zanzana/server/server.go
deleted file mode 100644
index a0ae53af4d2..00000000000
--- a/pkg/services/authz/zanzana/server/server.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package server
-
-import (
- "context"
- "strings"
- "sync"
- "time"
-
- "github.com/fullstorydev/grpchan/inprocgrpc"
- authzv1 "github.com/grafana/authlib/authz/proto/v1"
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
-
- dashboardalpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v2alpha1"
- "github.com/grafana/grafana/pkg/infra/localcache"
- "github.com/grafana/grafana/pkg/infra/log"
- "github.com/grafana/grafana/pkg/infra/tracing"
- authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
- "github.com/grafana/grafana/pkg/services/authz/zanzana/common"
- "github.com/grafana/grafana/pkg/setting"
-)
-
-const cacheCleanInterval = 2 * time.Minute
-
-var _ authzv1.AuthzServiceServer = (*Server)(nil)
-var _ authzextv1.AuthzExtentionServiceServer = (*Server)(nil)
-
-type OpenFGAServer interface {
- openfgav1.OpenFGAServiceServer
- IsReady(ctx context.Context) (bool, error)
-}
-
-type Server struct {
- authzv1.UnimplementedAuthzServiceServer
- authzextv1.UnimplementedAuthzExtentionServiceServer
-
- openfga OpenFGAServer
- openfgaClient openfgav1.OpenFGAServiceClient
-
- cfg setting.ZanzanaServerSettings
- stores map[string]storeInfo
- storesMU *sync.Mutex
- cache *localcache.CacheService
-
- logger log.Logger
- tracer tracing.Tracer
-}
-
-type storeInfo struct {
- ID string
- ModelID string
-}
-
-func NewServer(cfg setting.ZanzanaServerSettings, openfga OpenFGAServer, logger log.Logger, tracer tracing.Tracer) (*Server, error) {
- channel := &inprocgrpc.Channel{}
- openfgav1.RegisterOpenFGAServiceServer(channel, openfga)
- openFGAClient := openfgav1.NewOpenFGAServiceClient(channel)
-
- s := &Server{
- openfga: openfga,
- openfgaClient: openFGAClient,
- storesMU: &sync.Mutex{},
- stores: make(map[string]storeInfo),
- cfg: cfg,
- cache: localcache.New(cfg.CheckQueryCacheTTL, cacheCleanInterval),
- logger: logger,
- tracer: tracer,
- }
-
- return s, nil
-}
-
-func (s *Server) IsHealthy(ctx context.Context) (bool, error) {
- return s.openfga.IsReady(ctx)
-}
-
-func (s *Server) getContextuals(subject string) (*openfgav1.ContextualTupleKeys, error) {
- contextuals := make([]*openfgav1.TupleKey, 0)
-
- if strings.HasPrefix(subject, common.TypeRenderService+":") {
- contextuals = append(
- contextuals,
- &openfgav1.TupleKey{
- User: subject,
- Relation: common.RelationSetView,
- Object: common.NewGroupResourceIdent(
- dashboardalpha1.DashboardResourceInfo.GroupResource().Group,
- dashboardalpha1.DashboardResourceInfo.GroupResource().Resource,
- "",
- ),
- },
- )
- }
-
- if len(contextuals) > 0 {
- return &openfgav1.ContextualTupleKeys{TupleKeys: contextuals}, nil
- }
-
- return nil, nil
-}
diff --git a/pkg/services/authz/zanzana/server/server_batch_check.go b/pkg/services/authz/zanzana/server/server_batch_check.go
deleted file mode 100644
index 8b9195b01c8..00000000000
--- a/pkg/services/authz/zanzana/server/server_batch_check.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package server
-
-import (
- "context"
-
- authzv1 "github.com/grafana/authlib/authz/proto/v1"
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
-
- authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
- "github.com/grafana/grafana/pkg/services/authz/zanzana/common"
-)
-
-func (s *Server) BatchCheck(ctx context.Context, r *authzextv1.BatchCheckRequest) (*authzextv1.BatchCheckResponse, error) {
- ctx, span := s.tracer.Start(ctx, "server.BatchCheck")
- defer span.End()
-
- if err := authorize(ctx, r.GetNamespace()); err != nil {
- return nil, err
- }
-
- batchRes := &authzextv1.BatchCheckResponse{
- Groups: make(map[string]*authzextv1.BatchCheckGroupResource),
- }
-
- store, err := s.getStoreInfo(ctx, r.GetNamespace())
- if err != nil {
- return nil, err
- }
-
- contextuals, err := s.getContextuals(r.GetSubject())
- if err != nil {
- return nil, err
- }
-
- groupResourceAccess := make(map[string]bool)
-
- for _, item := range r.GetItems() {
- res, err := s.batchCheckItem(ctx, r, item, contextuals, store, groupResourceAccess)
- if err != nil {
- return nil, err
- }
-
- groupResource := common.FormatGroupResource(item.GetGroup(), item.GetResource(), item.GetSubresource())
- if _, ok := batchRes.Groups[groupResource]; !ok {
- batchRes.Groups[groupResource] = &authzextv1.BatchCheckGroupResource{
- Items: make(map[string]bool),
- }
- }
- batchRes.Groups[groupResource].Items[item.GetName()] = res.GetAllowed()
- }
-
- return batchRes, nil
-}
-
-func (s *Server) batchCheckItem(
- ctx context.Context,
- r *authzextv1.BatchCheckRequest,
- item *authzextv1.BatchCheckItem,
- contextuals *openfgav1.ContextualTupleKeys,
- store *storeInfo,
- groupResourceAccess map[string]bool,
-) (*authzv1.CheckResponse, error) {
- var (
- relation = common.VerbMapping[item.GetVerb()]
- resource = common.NewResourceInfoFromBatchItem(item)
- groupResource = resource.GroupResource()
- )
-
- allowed, ok := groupResourceAccess[groupResource]
- if !ok {
- res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, resource, contextuals, store)
- if err != nil {
- return nil, err
- }
-
- allowed = res.GetAllowed()
- groupResourceAccess[groupResource] = res.GetAllowed()
- }
-
- if allowed {
- return &authzv1.CheckResponse{Allowed: true}, nil
- }
-
- if resource.IsGeneric() {
- return s.checkGeneric(ctx, r.GetSubject(), relation, resource, contextuals, store)
- }
-
- return s.checkTyped(ctx, r.GetSubject(), relation, resource, contextuals, store)
-}
diff --git a/pkg/services/authz/zanzana/server/server_batch_check_test.go b/pkg/services/authz/zanzana/server/server_batch_check_test.go
deleted file mode 100644
index 6cb08032baa..00000000000
--- a/pkg/services/authz/zanzana/server/server_batch_check_test.go
+++ /dev/null
@@ -1,193 +0,0 @@
-package server
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/grafana/grafana/pkg/apimachinery/utils"
- authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
- "github.com/grafana/grafana/pkg/services/authz/zanzana/common"
-)
-
-func testBatchCheck(t *testing.T, server *Server) {
- newReq := func(subject, verb, group, resource, subresource string, items []*authzextv1.BatchCheckItem) *authzextv1.BatchCheckRequest {
- for i, item := range items {
- items[i] = &authzextv1.BatchCheckItem{
- Verb: verb,
- Group: group,
- Resource: resource,
- Subresource: subresource,
- Name: item.GetName(),
- Folder: item.GetFolder(),
- }
- }
-
- return &authzextv1.BatchCheckRequest{
- Namespace: namespace,
- Subject: subject,
- Items: items,
- }
- }
-
- t.Run("user:1 should only be able to read resource:dashboard.grafana.app/dashboards/1", func(t *testing.T) {
- groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
- res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
- {Name: "1", Folder: "1"},
- {Name: "2", Folder: "2"},
- }))
- require.NoError(t, err)
- require.Len(t, res.Groups[groupResource].Items, 2)
-
- assert.True(t, res.Groups[groupResource].Items["1"])
- assert.False(t, res.Groups[groupResource].Items["2"])
- })
-
- t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/{1,2} through group_resource", func(t *testing.T) {
- groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
- res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
- {Name: "1", Folder: "1"},
- {Name: "2", Folder: "2"},
- }))
- require.NoError(t, err)
- assert.Len(t, res.Groups[groupResource].Items, 2)
- })
-
- t.Run("user:3 should be able to read resource:dashboard.grafana.app/dashboards/1 with set relation", func(t *testing.T) {
- groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
- res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
- {Name: "1", Folder: "1"},
- {Name: "2", Folder: "2"},
- }))
- require.NoError(t, err)
- require.Len(t, res.Groups[groupResource].Items, 2)
-
- assert.True(t, res.Groups[groupResource].Items["1"])
- assert.False(t, res.Groups[groupResource].Items["2"])
- })
-
- t.Run("user:4 should be able to read all dashboard.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) {
- groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
- res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
- {Name: "1", Folder: "1"},
- {Name: "2", Folder: "3"},
- {Name: "3", Folder: "2"},
- }))
- require.NoError(t, err)
- require.Len(t, res.Groups[groupResource].Items, 3)
-
- assert.True(t, res.Groups[groupResource].Items["1"])
- assert.True(t, res.Groups[groupResource].Items["2"])
- assert.False(t, res.Groups[groupResource].Items["3"])
- })
-
- t.Run("user:5 should be able to read resource:dashboard.grafana.app/dashboards/1 through folder with set relation", func(t *testing.T) {
- groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
- res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:5", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
- {Name: "1", Folder: "1"},
- {Name: "2", Folder: "2"},
- }))
- require.NoError(t, err)
- require.Len(t, res.Groups[groupResource].Items, 2)
-
- assert.True(t, res.Groups[groupResource].Items["1"])
- assert.False(t, res.Groups[groupResource].Items["2"])
- })
-
- t.Run("user:6 should be able to read folder 1", func(t *testing.T) {
- groupResource := common.FormatGroupResource(folderGroup, folderResource, "")
- res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:6", utils.VerbGet, folderGroup, folderResource, "", []*authzextv1.BatchCheckItem{
- {Name: "1"},
- {Name: "2"},
- }))
- require.NoError(t, err)
- require.Len(t, res.Groups[groupResource].Items, 2)
-
- assert.True(t, res.Groups[groupResource].Items["1"])
- assert.False(t, res.Groups[groupResource].Items["2"])
- })
-
- t.Run("user:7 should be able to read folder {1,2} through group_resource access", func(t *testing.T) {
- groupResource := common.FormatGroupResource(folderGroup, folderResource, "")
- res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", []*authzextv1.BatchCheckItem{
- {Name: "1"},
- {Name: "2"},
- }))
- require.NoError(t, err)
- require.Len(t, res.Groups[groupResource].Items, 2)
- require.True(t, res.Groups[groupResource].Items["1"])
- require.True(t, res.Groups[groupResource].Items["2"])
- })
-
- t.Run("user:8 should be able to read all resoruce:dashboard.grafana.app/dashboards in folder 6 through folder 5", func(t *testing.T) {
- groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
- res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
- {Name: "10", Folder: "6"},
- {Name: "20", Folder: "6"},
- }))
- require.NoError(t, err)
- require.Len(t, res.Groups[groupResource].Items, 2)
- require.True(t, res.Groups[groupResource].Items["10"])
- require.True(t, res.Groups[groupResource].Items["20"])
- })
-
- t.Run("user:9 should be able to create dashboards in folder 6 through folder 5", func(t *testing.T) {
- groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, "")
- res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:9", utils.VerbCreate, dashboardGroup, dashboardResource, "", []*authzextv1.BatchCheckItem{
- {Name: "10", Folder: "6"},
- {Name: "20", Folder: "6"},
- }))
- require.NoError(t, err)
- t.Log(res.Groups)
- require.Len(t, res.Groups[groupResource].Items, 2)
- require.True(t, res.Groups[groupResource].Items["10"])
- require.True(t, res.Groups[groupResource].Items["20"])
- })
-
- t.Run("user:10 should be able to get dashboard status for 10 and 11", func(t *testing.T) {
- groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, statusSubresource)
- res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:10", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, []*authzextv1.BatchCheckItem{
- {Name: "10", Folder: "6"},
- {Name: "11", Folder: "6"},
- {Name: "12", Folder: "6"},
- }))
- require.NoError(t, err)
- t.Log(res.Groups)
- require.Len(t, res.Groups[groupResource].Items, 3)
- require.True(t, res.Groups[groupResource].Items["10"])
- require.True(t, res.Groups[groupResource].Items["11"])
- require.False(t, res.Groups[groupResource].Items["12"])
- })
-
- t.Run("user:11 should be able to get dashboard status for 10, 11 and 12 through group_resource", func(t *testing.T) {
- groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, statusSubresource)
- res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:11", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, []*authzextv1.BatchCheckItem{
- {Name: "10", Folder: "6"},
- {Name: "11", Folder: "6"},
- {Name: "12", Folder: "6"},
- }))
- require.NoError(t, err)
- t.Log(res.Groups)
- require.Len(t, res.Groups[groupResource].Items, 3)
- require.True(t, res.Groups[groupResource].Items["10"])
- require.True(t, res.Groups[groupResource].Items["11"])
- require.True(t, res.Groups[groupResource].Items["12"])
- })
-
- t.Run("user:12 should be able to get dashboard status in folder 5 and 6", func(t *testing.T) {
- groupResource := common.FormatGroupResource(dashboardGroup, dashboardResource, statusSubresource)
- res, err := server.BatchCheck(newContextWithNamespace(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, []*authzextv1.BatchCheckItem{
- {Name: "10", Folder: "5"},
- {Name: "11", Folder: "6"},
- {Name: "12", Folder: "6"},
- {Name: "13", Folder: "1"},
- }))
- require.NoError(t, err)
- require.Len(t, res.Groups[groupResource].Items, 4)
- require.True(t, res.Groups[groupResource].Items["10"])
- require.True(t, res.Groups[groupResource].Items["11"])
- require.True(t, res.Groups[groupResource].Items["12"])
- require.False(t, res.Groups[groupResource].Items["13"])
- })
-}
diff --git a/pkg/services/authz/zanzana/server/server_check.go b/pkg/services/authz/zanzana/server/server_check.go
deleted file mode 100644
index 838a39aba83..00000000000
--- a/pkg/services/authz/zanzana/server/server_check.go
+++ /dev/null
@@ -1,157 +0,0 @@
-package server
-
-import (
- "context"
-
- authzv1 "github.com/grafana/authlib/authz/proto/v1"
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
-
- "github.com/grafana/grafana/pkg/services/authz/zanzana/common"
-)
-
-func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.CheckResponse, error) {
- ctx, span := s.tracer.Start(ctx, "server.Check")
- defer span.End()
-
- if err := authorize(ctx, r.GetNamespace()); err != nil {
- return nil, err
- }
-
- store, err := s.getStoreInfo(ctx, r.GetNamespace())
- if err != nil {
- return nil, err
- }
-
- relation := common.VerbMapping[r.GetVerb()]
-
- contextuals, err := s.getContextuals(r.GetSubject())
- if err != nil {
- return nil, err
- }
-
- resource := common.NewResourceInfoFromCheck(r)
- res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, resource, contextuals, store)
- if err != nil {
- return nil, err
- }
-
- if res.GetAllowed() {
- return res, nil
- }
-
- if resource.IsGeneric() {
- return s.checkGeneric(ctx, r.GetSubject(), relation, resource, contextuals, store)
- }
-
- return s.checkTyped(ctx, r.GetSubject(), relation, resource, contextuals, store)
-}
-
-// checkGroupResource check if subject has access to the full "GroupResource", if they do they can access every object
-// within it.
-func (s *Server) checkGroupResource(ctx context.Context, subject, relation string, resource common.ResourceInfo, contextuals *openfgav1.ContextualTupleKeys, store *storeInfo) (*authzv1.CheckResponse, error) {
- if !common.IsGroupResourceRelation(relation) {
- return &authzv1.CheckResponse{Allowed: false}, nil
- }
-
- res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{
- StoreId: store.ID,
- AuthorizationModelId: store.ModelID,
- TupleKey: &openfgav1.CheckRequestTupleKey{
- User: subject,
- Relation: relation,
- Object: resource.GroupResourceIdent(),
- },
- ContextualTuples: contextuals,
- })
- if err != nil {
- return nil, err
- }
-
- return &authzv1.CheckResponse{Allowed: res.GetAllowed()}, nil
-}
-
-// checkTyped checks on our typed resources e.g. folder.
-func (s *Server) checkTyped(ctx context.Context, subject, relation string, resource common.ResourceInfo, contextuals *openfgav1.ContextualTupleKeys, store *storeInfo) (*authzv1.CheckResponse, error) {
- if !resource.IsValidRelation(relation) {
- return &authzv1.CheckResponse{Allowed: false}, nil
- }
-
- // Check if subject has direct access to resource
- res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{
- StoreId: store.ID,
- AuthorizationModelId: store.ModelID,
- TupleKey: &openfgav1.CheckRequestTupleKey{
- User: subject,
- Relation: relation,
- Object: resource.ResourceIdent(),
- },
- ContextualTuples: contextuals,
- })
- if err != nil {
- return nil, err
- }
-
- if res.GetAllowed() {
- return &authzv1.CheckResponse{Allowed: true}, nil
- }
-
- return &authzv1.CheckResponse{Allowed: false}, nil
-}
-
-// checkGeneric check our generic "resource" type. It checks:
-// 1. If subject has access as a sub resource for a folder.
-// 2. If subject has direct access to resource.
-func (s *Server) checkGeneric(ctx context.Context, subject, relation string, resource common.ResourceInfo, contextuals *openfgav1.ContextualTupleKeys, store *storeInfo) (*authzv1.CheckResponse, error) {
- var (
- folderIdent = resource.FolderIdent()
- resourceCtx = resource.Context()
- folderRelation = common.FolderResourceRelation(relation)
- )
-
- if folderIdent != "" && common.IsFolderResourceRelation(folderRelation) {
- // Check if subject has access as a sub resource for the folder
- res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{
- StoreId: store.ID,
- AuthorizationModelId: store.ModelID,
- TupleKey: &openfgav1.CheckRequestTupleKey{
- User: subject,
- Relation: folderRelation,
- Object: folderIdent,
- },
- Context: resourceCtx,
- ContextualTuples: contextuals,
- })
-
- if err != nil {
- return nil, err
- }
-
- if res.GetAllowed() {
- return &authzv1.CheckResponse{Allowed: res.GetAllowed()}, nil
- }
- }
-
- resourceIdent := resource.ResourceIdent()
- if !resource.IsValidRelation(relation) || resourceIdent == "" {
- return &authzv1.CheckResponse{Allowed: false}, nil
- }
-
- // Check if subject has direct access to resource
- res, err := s.openfga.Check(ctx, &openfgav1.CheckRequest{
- StoreId: store.ID,
- AuthorizationModelId: store.ModelID,
- TupleKey: &openfgav1.CheckRequestTupleKey{
- User: subject,
- Relation: relation,
- Object: resourceIdent,
- },
- Context: resourceCtx,
- ContextualTuples: contextuals,
- })
-
- if err != nil {
- return nil, err
- }
-
- return &authzv1.CheckResponse{Allowed: res.GetAllowed()}, nil
-}
diff --git a/pkg/services/authz/zanzana/server/server_check_test.go b/pkg/services/authz/zanzana/server/server_check_test.go
deleted file mode 100644
index 38cc1198e48..00000000000
--- a/pkg/services/authz/zanzana/server/server_check_test.go
+++ /dev/null
@@ -1,155 +0,0 @@
-package server
-
-import (
- "testing"
-
- authzv1 "github.com/grafana/authlib/authz/proto/v1"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/grafana/grafana/pkg/apimachinery/utils"
-)
-
-func testCheck(t *testing.T, server *Server) {
- newReq := func(subject, verb, group, resource, subresource, folder, name string) *authzv1.CheckRequest {
- return &authzv1.CheckRequest{
- Namespace: namespace,
- Subject: subject,
- Verb: verb,
- Group: group,
- Resource: resource,
- Subresource: subresource,
- Name: name,
- Folder: folder,
- }
- }
-
- t.Run("user:1 should only be able to read resource:dashboard.grafana.app/dashboards/1", func(t *testing.T) {
- res, err := server.Check(newContextWithNamespace(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
-
- // sanity check
- res, err = server.Check(newContextWithNamespace(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "2"))
- require.NoError(t, err)
- assert.False(t, res.GetAllowed())
-
- // sanity check no access to subresource
- res, err = server.Check(newContextWithNamespace(), newReq("user:1", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "1", "1"))
- require.NoError(t, err)
- assert.False(t, res.GetAllowed())
- })
-
- t.Run("user:2 should be able to read resource:dashboard.grafana.app/dashboards/1 through group_resource", func(t *testing.T) {
- res, err := server.Check(newContextWithNamespace(), newReq("user:2", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
- })
-
- t.Run("user:3 should be able to read resource:dashboard.grafana.app/dashboards/1 with set relation", func(t *testing.T) {
- res, err := server.Check(newContextWithNamespace(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
-
- // sanity check
- res, err = server.Check(newContextWithNamespace(), newReq("user:3", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "2"))
- require.NoError(t, err)
- assert.False(t, res.GetAllowed())
- })
-
- t.Run("user:4 should be able to read all dashboard.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) {
- res, err := server.Check(newContextWithNamespace(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
-
- res, err = server.Check(newContextWithNamespace(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "3", "2"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
-
- // sanity check
- res, err = server.Check(newContextWithNamespace(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "2"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
-
- res, err = server.Check(newContextWithNamespace(), newReq("user:4", utils.VerbGet, dashboardGroup, dashboardResource, "", "2", "2"))
- require.NoError(t, err)
- assert.False(t, res.GetAllowed())
- })
-
- t.Run("user:5 should be able to read resource:dashboard.grafana.app/dashboards/1 through folder with set relation", func(t *testing.T) {
- res, err := server.Check(newContextWithNamespace(), newReq("user:5", utils.VerbGet, dashboardGroup, dashboardResource, "", "1", "1"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
- })
-
- t.Run("user:6 should be able to read folder 1 ", func(t *testing.T) {
- res, err := server.Check(newContextWithNamespace(), newReq("user:6", utils.VerbGet, folderGroup, folderResource, "", "", "1"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
- })
-
- t.Run("user:7 should be able to read folder one through group_resource access", func(t *testing.T) {
- res, err := server.Check(newContextWithNamespace(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", "", "1"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
-
- res, err = server.Check(newContextWithNamespace(), newReq("user:7", utils.VerbGet, folderGroup, folderResource, "", "", "10"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
- })
-
- t.Run("user:8 should be able to read all resoruce:dashboard.grafana.app/dashboar in folder 6 through folder 5", func(t *testing.T) {
- res, err := server.Check(newContextWithNamespace(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "", "6", "10"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
-
- res, err = server.Check(newContextWithNamespace(), newReq("user:8", utils.VerbGet, dashboardGroup, dashboardResource, "", "5", "11"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
-
- res, err = server.Check(newContextWithNamespace(), newReq("user:8", utils.VerbGet, folderGroup, folderResource, "", "4", "12"))
- require.NoError(t, err)
- assert.False(t, res.GetAllowed())
- })
-
- t.Run("user:9 should be able to create dashboards in folder 5", func(t *testing.T) {
- res, err := server.Check(newContextWithNamespace(), newReq("user:9", utils.VerbCreate, dashboardGroup, dashboardResource, "", "5", ""))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
- })
-
- t.Run("user:10 should be able to read dashboard status for dashboard 10", func(t *testing.T) {
- res, err := server.Check(newContextWithNamespace(), newReq("user:10", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "", "10"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
-
- res, err = server.Check(newContextWithNamespace(), newReq("user:10", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "", "1"))
- require.NoError(t, err)
- assert.False(t, res.GetAllowed())
- })
-
- t.Run("user:11 should be able to read dashboard status for dashboard 10 through group_resource", func(t *testing.T) {
- res, err := server.Check(newContextWithNamespace(), newReq("user:11", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "", "10"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
- })
-
- t.Run("user:12 should be able to read dashboard status for all dashboards in folder 5", func(t *testing.T) {
- res, err := server.Check(newContextWithNamespace(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "5", "10"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
-
- res, err = server.Check(newContextWithNamespace(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "5", "11"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
-
- // inherited from folder 5
- res, err = server.Check(newContextWithNamespace(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "6", "12"))
- require.NoError(t, err)
- assert.True(t, res.GetAllowed())
-
- res, err = server.Check(newContextWithNamespace(), newReq("user:12", utils.VerbGet, dashboardGroup, dashboardResource, statusSubresource, "1", "13"))
- require.NoError(t, err)
- assert.False(t, res.GetAllowed())
- })
-}
diff --git a/pkg/services/authz/zanzana/server/server_list.go b/pkg/services/authz/zanzana/server/server_list.go
deleted file mode 100644
index c435cbc90cb..00000000000
--- a/pkg/services/authz/zanzana/server/server_list.go
+++ /dev/null
@@ -1,232 +0,0 @@
-package server
-
-import (
- "context"
- "encoding/base64"
- "errors"
- "hash/fnv"
- "io"
- "strings"
-
- authzv1 "github.com/grafana/authlib/authz/proto/v1"
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
-
- "github.com/grafana/grafana/pkg/services/authz/zanzana/common"
-)
-
-func (s *Server) List(ctx context.Context, r *authzv1.ListRequest) (*authzv1.ListResponse, error) {
- ctx, span := s.tracer.Start(ctx, "server.List")
- defer span.End()
-
- if err := authorize(ctx, r.GetNamespace()); err != nil {
- return nil, err
- }
-
- store, err := s.getStoreInfo(ctx, r.GetNamespace())
- if err != nil {
- return nil, err
- }
-
- contextuals, err := s.getContextuals(r.GetSubject())
- if err != nil {
- return nil, err
- }
-
- relation := common.VerbMapping[r.GetVerb()]
- resource := common.NewResourceInfoFromList(r)
-
- res, err := s.checkGroupResource(ctx, r.GetSubject(), relation, resource, contextuals, store)
- if err != nil {
- return nil, err
- }
-
- if res.GetAllowed() {
- return &authzv1.ListResponse{All: true}, nil
- }
-
- if resource.IsGeneric() {
- return s.listGeneric(ctx, r.GetSubject(), relation, resource, contextuals, store)
- }
-
- return s.listTyped(ctx, r.GetSubject(), relation, resource, contextuals, store)
-}
-
-func (s *Server) listTyped(ctx context.Context, subject, relation string, resource common.ResourceInfo, contextuals *openfgav1.ContextualTupleKeys, store *storeInfo) (*authzv1.ListResponse, error) {
- if !resource.IsValidRelation(relation) {
- return &authzv1.ListResponse{}, nil
- }
-
- // List all resources user has access too
- res, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{
- StoreId: store.ID,
- AuthorizationModelId: store.ModelID,
- Type: resource.Type(),
- Relation: relation,
- User: subject,
- ContextualTuples: contextuals,
- })
- if err != nil {
- return nil, err
- }
-
- return &authzv1.ListResponse{
- Items: typedObjects(resource.Type(), res.GetObjects()),
- }, nil
-}
-
-func (s *Server) listGeneric(ctx context.Context, subject, relation string, resource common.ResourceInfo, contextuals *openfgav1.ContextualTupleKeys, store *storeInfo) (*authzv1.ListResponse, error) {
- var (
- folderRelation = common.FolderResourceRelation(relation)
- resourceCtx = resource.Context()
- )
-
- // 1. List all folders subject has access to resource type in
- var folders []string
- if common.IsFolderResourceRelation(folderRelation) {
- res, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{
- StoreId: store.ID,
- AuthorizationModelId: store.ModelID,
- Type: common.TypeFolder,
- Relation: folderRelation,
- User: subject,
- Context: resourceCtx,
- ContextualTuples: contextuals,
- })
-
- if err != nil {
- return nil, err
- }
-
- folders = res.GetObjects()
- }
-
- // 2. List all resource directly assigned to subject
- var objects []string
- if resource.IsValidRelation(relation) {
- res, err := s.listObjects(ctx, &openfgav1.ListObjectsRequest{
- StoreId: store.ID,
- AuthorizationModelId: store.ModelID,
- Type: common.TypeResource,
- Relation: relation,
- User: subject,
- Context: resourceCtx,
- ContextualTuples: contextuals,
- })
- if err != nil {
- return nil, err
- }
-
- objects = res.GetObjects()
- }
-
- return &authzv1.ListResponse{
- Folders: folderObject(folders),
- Items: genericObjects(resource.GroupResource(), objects),
- }, nil
-}
-
-func (s *Server) listObjects(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) {
- fn := s.openfga.ListObjects
- if s.cfg.UseStreamedListObjects {
- fn = s.streamedListObjects
- }
-
- if s.cfg.CheckQueryCache {
- return s.listObjectCached(ctx, req, fn)
- }
-
- return fn(ctx, req)
-}
-
-type listFn func(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error)
-
-func (s *Server) listObjectCached(ctx context.Context, req *openfgav1.ListObjectsRequest, fn listFn) (*openfgav1.ListObjectsResponse, error) {
- ctx, span := s.tracer.Start(ctx, "server.listObjectCached")
- defer span.End()
-
- key, err := getRequestHash(req)
- if err != nil {
- return nil, err
- }
-
- if res, ok := s.cache.Get(key); ok {
- return res.(*openfgav1.ListObjectsResponse), nil
- }
-
- res, err := fn(ctx, req)
- if err != nil {
- return nil, err
- }
-
- s.cache.Set(key, res, 0)
- return res, nil
-}
-
-func (s *Server) streamedListObjects(ctx context.Context, req *openfgav1.ListObjectsRequest) (*openfgav1.ListObjectsResponse, error) {
- ctx, span := s.tracer.Start(ctx, "server.streamedListObjects")
- defer span.End()
-
- r := &openfgav1.StreamedListObjectsRequest{
- StoreId: req.GetStoreId(),
- AuthorizationModelId: req.GetAuthorizationModelId(),
- Type: req.GetType(),
- Relation: req.GetRelation(),
- User: req.GetUser(),
- Context: req.GetContext(),
- ContextualTuples: req.ContextualTuples,
- }
-
- stream, err := s.openfgaClient.StreamedListObjects(ctx, r)
- if err != nil {
- return nil, err
- }
-
- var objects []string
- for {
- res, err := stream.Recv()
- if err != nil {
- if errors.Is(err, io.EOF) {
- break
- }
- return nil, err
- }
- objects = append(objects, res.GetObject())
- }
-
- return &openfgav1.ListObjectsResponse{
- Objects: objects,
- }, nil
-}
-
-func getRequestHash(req *openfgav1.ListObjectsRequest) (string, error) {
- hash := fnv.New64a()
- _, err := hash.Write([]byte(req.String()))
- if err != nil {
- return "", err
- }
-
- return base64.StdEncoding.EncodeToString(hash.Sum(nil)), nil
-}
-
-func typedObjects(typ string, objects []string) []string {
- prefix := typ + ":"
- for i := range objects {
- objects[i] = strings.TrimPrefix(objects[i], prefix)
- }
- return objects
-}
-
-func genericObjects(gr string, objects []string) []string {
- prefix := common.TypeResourcePrefix + gr + "/"
- for i := range objects {
- objects[i] = strings.TrimPrefix(objects[i], prefix)
- }
- return objects
-}
-
-func folderObject(objects []string) []string {
- for i := range objects {
- objects[i] = strings.TrimPrefix(objects[i], common.TypeFolderPrefix)
- }
- return objects
-}
diff --git a/pkg/services/authz/zanzana/server/server_list_test.go b/pkg/services/authz/zanzana/server/server_list_test.go
deleted file mode 100644
index 92a115a8bed..00000000000
--- a/pkg/services/authz/zanzana/server/server_list_test.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package server
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- authzv1 "github.com/grafana/authlib/authz/proto/v1"
-
- "github.com/grafana/grafana/pkg/apimachinery/utils"
-)
-
-func testList(t *testing.T, server *Server) {
- newList := func(subject, group, resource, subresource string) *authzv1.ListRequest {
- return &authzv1.ListRequest{
- Namespace: namespace,
- Verb: utils.VerbList,
- Subject: subject,
- Group: group,
- Resource: resource,
- Subresource: subresource,
- }
- }
-
- t.Run("user:1 should list resource:dashboard.grafana.app/dashboards/1", func(t *testing.T) {
- res, err := server.List(newContextWithNamespace(), newList("user:1", dashboardGroup, dashboardResource, ""))
- require.NoError(t, err)
- assert.Len(t, res.GetItems(), 1)
- assert.Len(t, res.GetFolders(), 0)
- assert.Equal(t, res.GetItems()[0], "1")
- })
-
- t.Run("user:2 should be able to list all through group", func(t *testing.T) {
- res, err := server.List(newContextWithNamespace(), newList("user:2", dashboardGroup, dashboardResource, ""))
- require.NoError(t, err)
- assert.True(t, res.GetAll())
- assert.Len(t, res.GetItems(), 0)
- assert.Len(t, res.GetFolders(), 0)
- })
-
- t.Run("user:3 should be able to list resource:dashboard.grafana.app/dashboards/1 with set relation", func(t *testing.T) {
- res, err := server.List(newContextWithNamespace(), newList("user:3", dashboardGroup, dashboardResource, ""))
- require.NoError(t, err)
-
- assert.Len(t, res.GetItems(), 1)
- assert.Len(t, res.GetFolders(), 0)
- assert.Equal(t, res.GetItems()[0], "1")
- })
-
- t.Run("user:4 should be able to list all dashboard.grafana.app/dashboards in folder 1 and 3", func(t *testing.T) {
- res, err := server.List(newContextWithNamespace(), newList("user:4", dashboardGroup, dashboardResource, ""))
- require.NoError(t, err)
- assert.Len(t, res.GetItems(), 0)
- assert.Len(t, res.GetFolders(), 2)
-
- assert.Contains(t, res.GetFolders(), "1")
- assert.Contains(t, res.GetFolders(), "3")
- })
-
- t.Run("user:5 should be list all dashboard.grafana.app/dashboards in folder 1 with set relation", func(t *testing.T) {
- res, err := server.List(newContextWithNamespace(), newList("user:5", dashboardGroup, dashboardResource, ""))
- require.NoError(t, err)
- assert.Len(t, res.GetItems(), 0)
- assert.Len(t, res.GetFolders(), 1)
- assert.Equal(t, res.GetFolders()[0], "1")
- })
-
- t.Run("user:6 should be able to list folder 1", func(t *testing.T) {
- res, err := server.List(newContextWithNamespace(), newList("user:6", folderGroup, folderResource, ""))
- require.NoError(t, err)
- assert.Len(t, res.GetItems(), 1)
- assert.Len(t, res.GetFolders(), 0)
- assert.Equal(t, res.GetItems()[0], "1")
- })
-
- t.Run("user:7 should be able to list all folders", func(t *testing.T) {
- res, err := server.List(newContextWithNamespace(), newList("user:7", folderGroup, folderResource, ""))
- require.NoError(t, err)
- assert.Len(t, res.GetItems(), 0)
- assert.Len(t, res.GetFolders(), 0)
- assert.True(t, res.GetAll())
- })
-
- t.Run("user:8 should be able to list resoruce:dashboard.grafana.app/dashboard in folder 6 and folder 5", func(t *testing.T) {
- res, err := server.List(newContextWithNamespace(), newList("user:8", dashboardGroup, dashboardResource, ""))
- require.NoError(t, err)
- assert.Len(t, res.GetFolders(), 2)
-
- assert.Contains(t, res.GetFolders(), "5")
- assert.Contains(t, res.GetFolders(), "6")
- })
-
- t.Run("user:10 should be able to get resoruce:dashboard.grafana.app/dashboard/status for 10 and 11", func(t *testing.T) {
- res, err := server.List(newContextWithNamespace(), newList("user:10", dashboardGroup, dashboardResource, statusSubresource))
- require.NoError(t, err)
- assert.Len(t, res.GetFolders(), 0)
- assert.Len(t, res.GetItems(), 2)
-
- assert.Contains(t, res.GetItems(), "10")
- assert.Contains(t, res.GetItems(), "11")
- })
-
- t.Run("user:11 should be able to list all resoruce:dashboard.grafana.app/dashboard/status ", func(t *testing.T) {
- res, err := server.List(newContextWithNamespace(), newList("user:11", dashboardGroup, dashboardResource, statusSubresource))
- require.NoError(t, err)
- assert.Len(t, res.GetItems(), 0)
- assert.Len(t, res.GetFolders(), 0)
- assert.True(t, res.GetAll())
- })
-
- t.Run("user:12 should be able to list all resoruce:dashboard.grafana.app/dashboard/status in folder 5 and 6", func(t *testing.T) {
- res, err := server.List(newContextWithNamespace(), newList("user:12", dashboardGroup, dashboardResource, statusSubresource))
- require.NoError(t, err)
- assert.Len(t, res.GetItems(), 0)
- assert.Len(t, res.GetFolders(), 2)
-
- assert.Contains(t, res.GetFolders(), "5")
- assert.Contains(t, res.GetFolders(), "6")
- })
-}
diff --git a/pkg/services/authz/zanzana/server/server_read.go b/pkg/services/authz/zanzana/server/server_read.go
deleted file mode 100644
index 755f769c11b..00000000000
--- a/pkg/services/authz/zanzana/server/server_read.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package server
-
-import (
- "context"
-
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
-
- authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
- "github.com/grafana/grafana/pkg/services/authz/zanzana/common"
-)
-
-func (s *Server) Read(ctx context.Context, req *authzextv1.ReadRequest) (*authzextv1.ReadResponse, error) {
- ctx, span := s.tracer.Start(ctx, "server.Read")
- defer span.End()
-
- if err := authorize(ctx, req.GetNamespace()); err != nil {
- return nil, err
- }
-
- storeInf, err := s.getStoreInfo(ctx, req.Namespace)
- if err != nil {
- return nil, err
- }
-
- readReq := &openfgav1.ReadRequest{
- StoreId: storeInf.ID,
- PageSize: req.GetPageSize(),
- ContinuationToken: req.GetContinuationToken(),
- }
-
- if req.TupleKey != nil {
- readReq.TupleKey = &openfgav1.ReadRequestTupleKey{
- User: req.GetTupleKey().GetUser(),
- Relation: req.GetTupleKey().GetRelation(),
- Object: req.GetTupleKey().GetObject(),
- }
- }
-
- res, err := s.openfga.Read(ctx, readReq)
- if err != nil {
- return nil, err
- }
-
- tuples := make([]*authzextv1.Tuple, 0)
- for _, t := range res.GetTuples() {
- tuples = append(tuples, &authzextv1.Tuple{
- Key: common.ToAuthzExtTupleKey(t.GetKey()),
- Timestamp: t.GetTimestamp(),
- })
- }
-
- return &authzextv1.ReadResponse{
- Tuples: tuples,
- ContinuationToken: res.GetContinuationToken(),
- }, nil
-}
diff --git a/pkg/services/authz/zanzana/server/server_store.go b/pkg/services/authz/zanzana/server/server_store.go
deleted file mode 100644
index 22a10dced71..00000000000
--- a/pkg/services/authz/zanzana/server/server_store.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package server
-
-import (
- "context"
- "fmt"
-
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
- "github.com/openfga/language/pkg/go/transformer"
- "google.golang.org/protobuf/types/known/wrapperspb"
-
- "github.com/grafana/grafana/pkg/services/authz/zanzana/schema"
-)
-
-func (s *Server) getStoreInfo(ctx context.Context, namespace string) (*storeInfo, error) {
- s.storesMU.Lock()
- defer s.storesMU.Unlock()
- info, ok := s.stores[namespace]
- if ok {
- return &info, nil
- }
-
- store, err := s.getOrCreateStore(ctx, namespace)
- if err != nil {
- return nil, err
- }
-
- modelID, err := s.loadModel(ctx, store.GetId(), schema.SchemaModules)
- if err != nil {
- return nil, err
- }
-
- info = storeInfo{
- ID: store.GetId(),
- ModelID: modelID,
- }
-
- s.stores[namespace] = info
-
- return &info, nil
-}
-
-func (s *Server) getOrCreateStore(ctx context.Context, namespace string) (*openfgav1.Store, error) {
- res, err := s.openfga.ListStores(ctx, &openfgav1.ListStoresRequest{Name: namespace})
- if err != nil {
- return nil, fmt.Errorf("failed to load zanzana stores: %w", err)
- }
-
- for _, s := range res.GetStores() {
- if s.GetName() == namespace {
- return s, nil
- }
- }
-
- createStoreRes, err := s.openfga.CreateStore(ctx, &openfgav1.CreateStoreRequest{Name: namespace})
- if err != nil {
- return nil, err
- }
-
- return &openfgav1.Store{
- Id: createStoreRes.GetId(),
- Name: createStoreRes.GetName(),
- }, nil
-}
-
-func (s *Server) loadModel(ctx context.Context, storeID string, modules []transformer.ModuleFile) (string, error) {
- var continuationToken string
-
- model, err := schema.TransformModulesToModel(modules)
- if err != nil {
- return "", err
- }
-
- // ReadAuthorizationModels returns authorization models for a store sorted in descending order of creation.
- // So with a pageSize of 1 we will get the latest model.
- res, err := s.openfga.ReadAuthorizationModels(ctx, &openfgav1.ReadAuthorizationModelsRequest{
- StoreId: storeID,
- PageSize: &wrapperspb.Int32Value{Value: 1},
- ContinuationToken: continuationToken,
- })
-
- if err != nil {
- return "", fmt.Errorf("failed to load authorization model: %w", err)
- }
-
- for _, m := range res.GetAuthorizationModels() {
- // If provided dsl is equal to a stored dsl we use that as the authorization id
- if schema.EqualModels(m, model) {
- return m.GetId(), nil
- }
- }
-
- writeRes, err := s.openfga.WriteAuthorizationModel(ctx, &openfgav1.WriteAuthorizationModelRequest{
- StoreId: storeID,
- TypeDefinitions: model.GetTypeDefinitions(),
- SchemaVersion: model.GetSchemaVersion(),
- Conditions: model.GetConditions(),
- })
-
- if err != nil {
- return "", fmt.Errorf("failed to load authorization model: %w", err)
- }
-
- return writeRes.GetAuthorizationModelId(), nil
-}
diff --git a/pkg/services/authz/zanzana/server/server_test.go b/pkg/services/authz/zanzana/server/server_test.go
deleted file mode 100644
index 8a1fd1786ea..00000000000
--- a/pkg/services/authz/zanzana/server/server_test.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package server
-
-import (
- "context"
- "testing"
-
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
- "github.com/stretchr/testify/require"
-
- authnlib "github.com/grafana/authlib/authn"
- claims "github.com/grafana/authlib/types"
-
- "github.com/grafana/grafana/pkg/infra/db"
- "github.com/grafana/grafana/pkg/infra/log"
- "github.com/grafana/grafana/pkg/infra/tracing"
- "github.com/grafana/grafana/pkg/services/authz/zanzana/common"
- "github.com/grafana/grafana/pkg/services/authz/zanzana/store"
- "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
- "github.com/grafana/grafana/pkg/setting"
- "github.com/grafana/grafana/pkg/tests/testsuite"
-)
-
-const (
- namespace = "default"
-
- dashboardGroup = "dashboard.grafana.app"
- dashboardResource = "dashboards"
-
- folderGroup = "folder.grafana.app"
- folderResource = "folders"
-
- statusSubresource = "status"
-)
-
-func TestMain(m *testing.M) {
- testsuite.Run(m)
-}
-
-func TestIntegrationServer(t *testing.T) {
- if testing.Short() {
- t.Skip("skipping integration test")
- }
-
- testDB, cfg := db.InitTestDBWithCfg(t)
- // Hack to skip these tests on mysql 5.7
- if testDB.GetDialect().DriverName() == migrator.MySQL {
- if supported, err := testDB.RecursiveQueriesAreSupported(); !supported || err != nil {
- t.Skip("skipping integration test")
- }
- }
-
- srv := setup(t, testDB, cfg)
- t.Run("test check", func(t *testing.T) {
- testCheck(t, srv)
- })
-
- t.Run("test list", func(t *testing.T) {
- testList(t, srv)
- })
-
- t.Run("test list streaming", func(t *testing.T) {
- srv.cfg.UseStreamedListObjects = true
- testList(t, srv)
- srv.cfg.UseStreamedListObjects = false
- })
-
- t.Run("test batch check", func(t *testing.T) {
- testBatchCheck(t, srv)
- })
-}
-
-func setup(t *testing.T, testDB db.DB, cfg *setting.Cfg) *Server {
- t.Helper()
- store, err := store.NewEmbeddedStore(cfg, testDB, log.NewNopLogger())
- require.NoError(t, err)
- openfga, err := NewOpenFGAServer(cfg.ZanzanaServer, store, log.NewNopLogger())
- require.NoError(t, err)
-
- srv, err := NewServer(cfg.ZanzanaServer, openfga, log.NewNopLogger(), tracing.NewNoopTracerService())
- require.NoError(t, err)
-
- storeInf, err := srv.getStoreInfo(context.Background(), namespace)
- require.NoError(t, err)
-
- // seed tuples
- _, err = openfga.Write(context.Background(), &openfgav1.WriteRequest{
- StoreId: storeInf.ID,
- AuthorizationModelId: storeInf.ModelID,
- Writes: &openfgav1.WriteRequestWrites{
- TupleKeys: []*openfgav1.TupleKey{
- common.NewResourceTuple("user:1", common.RelationGet, dashboardGroup, dashboardResource, "", "1"),
- common.NewResourceTuple("user:1", common.RelationUpdate, dashboardGroup, dashboardResource, "", "1"),
- common.NewGroupResourceTuple("user:2", common.RelationGet, dashboardGroup, dashboardResource, ""),
- common.NewGroupResourceTuple("user:2", common.RelationUpdate, dashboardGroup, dashboardResource, ""),
- common.NewResourceTuple("user:3", common.RelationSetView, dashboardGroup, dashboardResource, "", "1"),
- common.NewFolderResourceTuple("user:4", common.RelationGet, dashboardGroup, dashboardResource, "", "1"),
- common.NewFolderResourceTuple("user:4", common.RelationGet, dashboardGroup, dashboardResource, "", "3"),
- common.NewFolderResourceTuple("user:5", common.RelationSetEdit, dashboardGroup, dashboardResource, "", "1"),
- common.NewFolderTuple("user:6", common.RelationGet, "1"),
- common.NewGroupResourceTuple("user:7", common.RelationGet, folderGroup, folderResource, ""),
- common.NewFolderParentTuple("5", "4"),
- common.NewFolderParentTuple("6", "5"),
- common.NewFolderResourceTuple("user:8", common.RelationSetEdit, dashboardGroup, dashboardResource, "", "5"),
- common.NewFolderResourceTuple("user:9", common.RelationCreate, dashboardGroup, dashboardResource, "", "5"),
- common.NewResourceTuple("user:10", common.RelationGet, dashboardGroup, dashboardResource, statusSubresource, "10"),
- common.NewResourceTuple("user:10", common.RelationGet, dashboardGroup, dashboardResource, statusSubresource, "11"),
- common.NewGroupResourceTuple("user:11", common.RelationGet, dashboardGroup, dashboardResource, statusSubresource),
- common.NewFolderResourceTuple("user:12", common.RelationGet, dashboardGroup, dashboardResource, statusSubresource, "5"),
- },
- },
- })
- require.NoError(t, err)
- return srv
-}
-
-func newContextWithNamespace() context.Context {
- ctx := context.Background()
- ctx = claims.WithAuthInfo(ctx, authnlib.NewAccessTokenAuthInfo(authnlib.Claims[authnlib.AccessTokenClaims]{
- Rest: authnlib.AccessTokenClaims{
- Namespace: "*",
- },
- }))
- return ctx
-}
diff --git a/pkg/services/authz/zanzana/server/server_write.go b/pkg/services/authz/zanzana/server/server_write.go
deleted file mode 100644
index 0aa932bc3f8..00000000000
--- a/pkg/services/authz/zanzana/server/server_write.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package server
-
-import (
- "context"
-
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
-
- authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
- "github.com/grafana/grafana/pkg/services/authz/zanzana/common"
-)
-
-func (s *Server) Write(ctx context.Context, req *authzextv1.WriteRequest) (*authzextv1.WriteResponse, error) {
- ctx, span := s.tracer.Start(ctx, "server.Write")
- defer span.End()
-
- if err := authorize(ctx, req.GetNamespace()); err != nil {
- return nil, err
- }
-
- storeInf, err := s.getStoreInfo(ctx, req.Namespace)
- if err != nil {
- return nil, err
- }
-
- writeTuples := make([]*openfgav1.TupleKey, 0)
- for _, t := range req.GetWrites().GetTupleKeys() {
- writeTuples = append(writeTuples, common.ToOpenFGATupleKey(t))
- }
-
- deleteTuples := make([]*openfgav1.TupleKeyWithoutCondition, 0)
- for _, t := range req.GetDeletes().GetTupleKeys() {
- deleteTuples = append(deleteTuples, common.ToOpenFGATupleKeyWithoutCondition(t))
- }
-
- writeReq := &openfgav1.WriteRequest{
- StoreId: storeInf.ID,
- AuthorizationModelId: storeInf.ModelID,
- }
- if len(writeTuples) > 0 {
- writeReq.Writes = &openfgav1.WriteRequestWrites{
- TupleKeys: writeTuples,
- }
- }
- if len(deleteTuples) > 0 {
- writeReq.Deletes = &openfgav1.WriteRequestDeletes{
- TupleKeys: deleteTuples,
- }
- }
-
- _, err = s.openfga.Write(ctx, writeReq)
- if err != nil {
- return nil, err
- }
-
- return &authzextv1.WriteResponse{}, nil
-}
diff --git a/pkg/services/authz/zanzana/store.go b/pkg/services/authz/zanzana/store.go
deleted file mode 100644
index 67361386744..00000000000
--- a/pkg/services/authz/zanzana/store.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package zanzana
-
-import (
- "github.com/openfga/openfga/pkg/storage"
-
- "github.com/grafana/grafana/pkg/infra/db"
- "github.com/grafana/grafana/pkg/infra/log"
- "github.com/grafana/grafana/pkg/setting"
-
- "github.com/grafana/grafana/pkg/services/authz/zanzana/store"
-)
-
-func NewStore(cfg *setting.Cfg, logger log.Logger) (storage.OpenFGADatastore, error) {
- return store.NewStore(cfg, logger)
-}
-func NewEmbeddedStore(cfg *setting.Cfg, db db.DB, logger log.Logger) (storage.OpenFGADatastore, error) {
- return store.NewEmbeddedStore(cfg, db, logger)
-}
diff --git a/pkg/services/authz/zanzana/store/migration/migrator.go b/pkg/services/authz/zanzana/store/migration/migrator.go
deleted file mode 100644
index 3a2db5aa8c9..00000000000
--- a/pkg/services/authz/zanzana/store/migration/migrator.go
+++ /dev/null
@@ -1,128 +0,0 @@
-package migration
-
-import (
- "embed"
- "errors"
- "fmt"
- "strings"
-
- "xorm.io/xorm"
-
- "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
- "github.com/grafana/grafana/pkg/setting"
-)
-
-func Run(cfg *setting.Cfg, typ, connStr string, fs embed.FS, path string) error {
- engine, err := xorm.NewEngine(typ, connStr)
- if err != nil {
- return fmt.Errorf("failed to create db engine: %w", err)
- }
-
- m := migrator.NewMigrator(engine, cfg)
- m.AddCreateMigration()
-
- if err := RunWithMigrator(m, cfg, fs, path); err != nil {
- return err
- }
-
- return engine.Close()
-}
-
-func RunWithMigrator(m *migrator.Migrator, cfg *setting.Cfg, fs embed.FS, path string) error {
- migrations, err := getMigrations(fs, path)
- if err != nil {
- return err
- }
-
- for _, mig := range migrations {
- m.AddMigration(mig.name, mig.migration)
- }
-
- sec := cfg.Raw.Section("database")
- return m.Start(
- sec.Key("migration_locking").MustBool(true),
- sec.Key("locking_attempt_timeout_sec").MustInt(),
- )
-}
-
-type migration struct {
- name string
- migration migrator.Migration
-}
-
-func getMigrations(fs embed.FS, path string) ([]migration, error) {
- entries, err := fs.ReadDir(path)
- if err != nil {
- return nil, fmt.Errorf("failed to read migration dir: %w", err)
- }
-
- // parseStatements extracts statements from a sql file so we can execute
- // them as separate migrations. OpenFGA uses Goose as their migration egine
- // and Goose uses a single sql file for both up and down migrations.
- // Grafana only supports up migration so we strip out the down migration
- // and parse each individual statement
- parseStatements := func(data []byte) ([]string, error) {
- scripts := strings.Split(strings.TrimPrefix(string(data), "-- +goose Up"), "-- +goose Down")
- if len(scripts) != 2 {
- return nil, errors.New("malformed migration file")
- }
-
- // We assume that up migrations are always before down migrations
- parts := strings.SplitAfter(scripts[0], ";")
- stmts := make([]string, 0, len(parts))
- for _, p := range parts {
- p = strings.TrimSpace(p)
- if p != "" {
- stmts = append(stmts, p)
- }
- }
-
- return stmts, nil
- }
-
- formatName := func(name string) string {
- // Each migration file start with XXX where X is a number.
- // We remove that part and prefix each migration with "zanzana".
- return strings.TrimSuffix("zanzana"+name[3:], ".sql")
- }
-
- migrations := make([]migration, 0, len(entries))
- for _, e := range entries {
- data, err := fs.ReadFile(path + "/" + e.Name())
- if err != nil {
- return nil, fmt.Errorf("failed to read migration file: %w", err)
- }
-
- stmts, err := parseStatements(data)
- if err != nil {
- return nil, fmt.Errorf("failed to parse migration: %w", err)
- }
-
- migrations = append(migrations, migration{
- name: formatName(e.Name()),
- migration: &rawMigration{stmts: stmts},
- })
- }
-
- return migrations, nil
-}
-
-var _ migrator.CodeMigration = (*rawMigration)(nil)
-
-type rawMigration struct {
- stmts []string
- migrator.MigrationBase
-}
-
-func (m *rawMigration) Exec(sess *xorm.Session, migrator *migrator.Migrator) error {
- for _, stmt := range m.stmts {
- if _, err := sess.Exec(stmt); err != nil {
- return fmt.Errorf("failed to run migration: %w", err)
- }
- }
- return nil
-}
-
-func (m *rawMigration) SQL(dialect migrator.Dialect) string {
- return strings.Join(m.stmts, "\n")
-}
diff --git a/pkg/services/authz/zanzana/store/store.go b/pkg/services/authz/zanzana/store/store.go
deleted file mode 100644
index 7e3f32e9d1c..00000000000
--- a/pkg/services/authz/zanzana/store/store.go
+++ /dev/null
@@ -1,125 +0,0 @@
-package store
-
-import (
- "fmt"
- "strings"
- "time"
-
- "github.com/openfga/openfga/assets"
- "github.com/openfga/openfga/pkg/storage"
- "github.com/openfga/openfga/pkg/storage/mysql"
- "github.com/openfga/openfga/pkg/storage/postgres"
- "github.com/openfga/openfga/pkg/storage/sqlcommon"
- "github.com/openfga/openfga/pkg/storage/sqlite"
-
- "github.com/grafana/grafana/pkg/infra/db"
- "github.com/grafana/grafana/pkg/infra/log"
- "github.com/grafana/grafana/pkg/services/sqlstore"
- "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
- "github.com/grafana/grafana/pkg/setting"
-
- zlogger "github.com/grafana/grafana/pkg/services/authz/zanzana/logger"
- "github.com/grafana/grafana/pkg/services/authz/zanzana/store/migration"
-)
-
-func NewStore(cfg *setting.Cfg, logger log.Logger) (storage.OpenFGADatastore, error) {
- grafanaDBCfg, zanzanaDBCfg, err := parseConfig(cfg, logger)
- if err != nil {
- return nil, fmt.Errorf("failed to parse database config: %w", err)
- }
-
- switch grafanaDBCfg.Type {
- case migrator.SQLite:
- connStr := sqliteConnectionString(grafanaDBCfg.ConnectionString)
- if err := migration.Run(cfg, migrator.SQLite, connStr, assets.EmbedMigrations, assets.SqliteMigrationDir); err != nil {
- return nil, fmt.Errorf("failed to run migrations: %w", err)
- }
-
- return sqlite.New(connStr, zanzanaDBCfg)
- case migrator.MySQL:
- // For mysql we need to pass parseTime parameter in connection string
- connStr := grafanaDBCfg.ConnectionString + "&parseTime=true"
- if err := migration.Run(cfg, migrator.MySQL, connStr, assets.EmbedMigrations, assets.MySQLMigrationDir); err != nil {
- return nil, fmt.Errorf("failed to run migrations: %w", err)
- }
-
- return mysql.New(connStr, zanzanaDBCfg)
- case migrator.Postgres:
- connStr := grafanaDBCfg.ConnectionString
- if err := migration.Run(cfg, migrator.Postgres, connStr, assets.EmbedMigrations, assets.PostgresMigrationDir); err != nil {
- return nil, fmt.Errorf("failed to run migrations: %w", err)
- }
-
- return postgres.New(connStr, zanzanaDBCfg)
- }
-
- // Should never happen
- return nil, fmt.Errorf("unsupported database engine: %s", grafanaDBCfg.Type)
-}
-
-func NewEmbeddedStore(cfg *setting.Cfg, db db.DB, logger log.Logger) (storage.OpenFGADatastore, error) {
- grafanaDBCfg, zanzanaDBCfg, err := parseConfig(cfg, logger)
- if err != nil {
- return nil, fmt.Errorf("failed to parse database config: %w", err)
- }
-
- switch grafanaDBCfg.Type {
- case migrator.SQLite:
- grafanaDBCfg.ConnectionString = sqliteConnectionString(grafanaDBCfg.ConnectionString)
- if err := migration.Run(cfg, migrator.SQLite, grafanaDBCfg.ConnectionString, assets.EmbedMigrations, assets.SqliteMigrationDir); err != nil {
- return nil, fmt.Errorf("failed to run migrations: %w", err)
- }
-
- return sqlite.New(grafanaDBCfg.ConnectionString, zanzanaDBCfg)
- case migrator.MySQL:
- m := migrator.NewMigrator(db.GetEngine(), cfg)
- if err := migration.RunWithMigrator(m, cfg, assets.EmbedMigrations, assets.MySQLMigrationDir); err != nil {
- return nil, fmt.Errorf("failed to run migrations: %w", err)
- }
-
- // For mysql we need to pass parseTime parameter in connection string
- return mysql.New(grafanaDBCfg.ConnectionString+"&parseTime=true", zanzanaDBCfg)
- case migrator.Postgres:
- m := migrator.NewMigrator(db.GetEngine(), cfg)
- if err := migration.RunWithMigrator(m, cfg, assets.EmbedMigrations, assets.PostgresMigrationDir); err != nil {
- return nil, fmt.Errorf("failed to run migrations: %w", err)
- }
-
- return postgres.New(grafanaDBCfg.ConnectionString, zanzanaDBCfg)
- }
-
- // Should never happen
- return nil, fmt.Errorf("unsupported database engine: %s", db.GetDialect().DriverName())
-}
-
-func parseConfig(cfg *setting.Cfg, logger log.Logger) (*sqlstore.DatabaseConfig, *sqlcommon.Config, error) {
- sec := cfg.Raw.Section("database")
- grafanaDBCfg, err := sqlstore.NewDatabaseConfig(cfg, nil)
- if err != nil {
- return nil, nil, nil
- }
-
- zanzanaDBCfg := &sqlcommon.Config{
- Logger: zlogger.New(logger),
- MaxTuplesPerWriteField: 100,
- MaxTypesPerModelField: 100,
- MaxOpenConns: grafanaDBCfg.MaxOpenConn,
- MaxIdleConns: grafanaDBCfg.MaxIdleConn,
- ConnMaxLifetime: time.Duration(grafanaDBCfg.ConnMaxLifetime) * time.Second,
- ExportMetrics: sec.Key("instrument_queries").MustBool(false),
- }
-
- return grafanaDBCfg, zanzanaDBCfg, nil
-}
-
-func sqliteConnectionString(v string) string {
- // handle test setup by replacing grafana-test with zanzana-test
- if strings.Contains(v, "grafana-test/grafana-test") {
- name := v[strings.LastIndex(v, "/")+1:]
- name = strings.Replace(name, "grafana-test", "zanzana-test", 1)
- return v[0:strings.LastIndex(v, "/")+1] + name
- }
-
- // hardcode zanzana.db for now
- return v[0:strings.LastIndex(v, "/")+1] + "zanzana.db"
-}
diff --git a/pkg/services/authz/zanzana/translations.go b/pkg/services/authz/zanzana/translations.go
deleted file mode 100644
index 70bf8337c6c..00000000000
--- a/pkg/services/authz/zanzana/translations.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package zanzana
-
-import (
- dashboardalpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
- folderalpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
-)
-
-const (
- roleGrafanaAdmin = "Grafana Admin"
- roleAdmin = "Admin"
- roleEditor = "Editor"
- roleViewer = "Viewer"
- roleNone = "None"
-)
-
-var basicRolesTranslations = map[string]string{
- roleGrafanaAdmin: "basic_grafana_admin",
- roleAdmin: "basic_admin",
- roleEditor: "basic_editor",
- roleViewer: "basic_viewer",
- roleNone: "basic_none",
-}
-
-type resourceTranslation struct {
- typ string
- group string
- resource string
- mapping map[string]actionMappig
-}
-
-type actionMappig struct {
- relation string
- group string
- resource string
- subresource string
-}
-
-func newMapping(relation, subresource string) actionMappig {
- return newScopedMapping(relation, "", "", subresource)
-}
-
-func newScopedMapping(relation, group, resource, subresource string) actionMappig {
- return actionMappig{relation, group, resource, subresource}
-}
-
-var (
- folderGroup = folderalpha1.FolderResourceInfo.GroupResource().Group
- folderResource = folderalpha1.FolderResourceInfo.GroupResource().Resource
-
- dashboardGroup = dashboardalpha1.DashboardResourceInfo.GroupResource().Group
- dashboardResource = dashboardalpha1.DashboardResourceInfo.GroupResource().Resource
-)
-
-var resourceTranslations = map[string]resourceTranslation{
- KindFolders: {
- typ: TypeFolder,
- group: folderGroup,
- resource: folderResource,
- mapping: map[string]actionMappig{
- "folders:read": newMapping(RelationGet, ""),
- "folders:write": newMapping(RelationUpdate, ""),
- "folders:create": newMapping(RelationCreate, ""),
- "folders:delete": newMapping(RelationDelete, ""),
- "dashboards:read": newScopedMapping(RelationGet, dashboardGroup, dashboardResource, ""),
- "dashboards:write": newScopedMapping(RelationUpdate, dashboardGroup, dashboardResource, ""),
- "dashboards:create": newScopedMapping(RelationCreate, dashboardGroup, dashboardResource, ""),
- "dashboards:delete": newScopedMapping(RelationDelete, dashboardGroup, dashboardResource, ""),
- },
- },
- KindDashboards: {
- typ: TypeResource,
- group: dashboardGroup,
- resource: dashboardResource,
- mapping: map[string]actionMappig{
- "dashboards:read": newMapping(RelationGet, ""),
- "dashboards:write": newMapping(RelationUpdate, ""),
- "dashboards:create": newMapping(RelationCreate, ""),
- "dashboards:delete": newMapping(RelationDelete, ""),
- },
- },
-}
diff --git a/pkg/services/authz/zanzana/zanzana.go b/pkg/services/authz/zanzana/zanzana.go
deleted file mode 100644
index 327618384af..00000000000
--- a/pkg/services/authz/zanzana/zanzana.go
+++ /dev/null
@@ -1,182 +0,0 @@
-package zanzana
-
-import (
- "fmt"
- "strings"
-
- openfgav1 "github.com/openfga/api/proto/openfga/v1"
-
- authlib "github.com/grafana/authlib/types"
-
- "github.com/grafana/grafana/pkg/services/authz/zanzana/common"
-)
-
-const (
- TypeUser = common.TypeUser
- TypeServiceAccount = common.TypeServiceAccount
- TypeRenderService = common.TypeRenderService
- TypeAnonymous = common.TypeAnonymous
- TypeTeam = common.TypeTeam
- TypeRole = common.TypeRole
- TypeFolder = common.TypeFolder
- TypeResource = common.TypeResource
- TypeNamespace = common.TypeGroupResouce
-)
-
-const (
- RelationTeamMember = common.RelationTeamMember
- RelationTeamAdmin = common.RelationTeamAdmin
- RelationParent = common.RelationParent
- RelationAssignee = common.RelationAssignee
-
- RelationSetView = common.RelationSetView
- RelationSetEdit = common.RelationSetEdit
- RelationSetAdmin = common.RelationSetAdmin
-
- RelationGet = common.RelationGet
- RelationUpdate = common.RelationUpdate
- RelationCreate = common.RelationCreate
- RelationDelete = common.RelationDelete
-
- RelationFolderResourceSetView = common.RelationFolderResourceSetView
- RelationFolderResourceSetEdit = common.RelationFolderResourceSetEdit
- RelationFolderResourceSetAdmin = common.RelationFolderResourceSetAdmin
-
- RelationFolderResourceRead = common.RelationFolderResourceGet
- RelationFolderResourceWrite = common.RelationFolderResourceUpdate
- RelationFolderResourceCreate = common.RelationFolderResourceCreate
- RelationFolderResourceDelete = common.RelationFolderResourceDelete
-)
-
-var (
- RelationsFolder = common.RelationsFolder
- RelationsResouce = common.RelationsResource
- RelationsFolderResource = common.RelationsFolderResource
-)
-
-const (
- KindDashboards string = "dashboards"
- KindFolders string = "folders"
-)
-
-var (
- ToAuthzExtTupleKey = common.ToAuthzExtTupleKey
- ToAuthzExtTupleKeys = common.ToAuthzExtTupleKeys
- ToAuthzExtTupleKeyWithoutCondition = common.ToAuthzExtTupleKeyWithoutCondition
- ToAuthzExtTupleKeysWithoutCondition = common.ToAuthzExtTupleKeysWithoutCondition
-
- ToOpenFGATuple = common.ToOpenFGATuple
- ToOpenFGATuples = common.ToOpenFGATuples
- ToOpenFGATupleKey = common.ToOpenFGATupleKey
- ToOpenFGATupleKeyWithoutCondition = common.ToOpenFGATupleKeyWithoutCondition
-)
-
-// NewTupleEntry constructs new openfga entry type:name[#relation].
-// Relation allows to specify group of users (subjects) related to type:name
-// (for example, team:devs#member refers to users which are members of team devs)
-func NewTupleEntry(objectType, name, relation string) string {
- obj := fmt.Sprintf("%s:%s", objectType, name)
- if relation != "" {
- obj = fmt.Sprintf("%s#%s", obj, relation)
- }
- return obj
-}
-
-func TranslateToResourceTuple(subject string, action, kind, name string) (*openfgav1.TupleKey, bool) {
- translation, ok := resourceTranslations[kind]
-
- if !ok {
- return nil, false
- }
-
- m, ok := translation.mapping[action]
- if !ok {
- return nil, false
- }
-
- if name == "*" {
- return common.NewGroupResourceTuple(subject, m.relation, translation.group, translation.resource, m.subresource), true
- }
-
- if translation.typ == TypeResource {
- return common.NewResourceTuple(subject, m.relation, translation.group, translation.resource, m.subresource, name), true
- }
-
- if translation.typ == TypeFolder {
- if m.group != "" && m.resource != "" {
- return common.NewFolderResourceTuple(subject, m.relation, m.group, m.resource, m.subresource, name), true
- }
-
- return common.NewFolderTuple(subject, m.relation, name), true
- }
-
- return common.NewTypedTuple(translation.typ, subject, m.relation, name), true
-}
-
-func IsFolderResourceTuple(t *openfgav1.TupleKey) bool {
- return strings.HasPrefix(t.Object, TypeFolder) && strings.HasPrefix(t.Relation, "resource_")
-}
-
-func MergeFolderResourceTuples(a, b *openfgav1.TupleKey) {
- va := a.Condition.Context.Fields["group_resources"]
- vb := b.Condition.Context.Fields["group_resources"]
- va.GetListValue().Values = append(va.GetListValue().Values, vb.GetListValue().Values...)
-}
-
-func TranslateToCheckRequest(namespace, action, kind, folder, name string) (*authlib.CheckRequest, bool) {
- translation, ok := resourceTranslations[kind]
-
- if !ok {
- return nil, false
- }
-
- m, ok := translation.mapping[action]
- if !ok {
- return nil, false
- }
-
- verb, ok := common.RelationToVerbMapping[m.relation]
- if !ok {
- return nil, false
- }
-
- req := &authlib.CheckRequest{
- Namespace: namespace,
- Verb: verb,
- Group: translation.group,
- Resource: translation.resource,
- Name: name,
- Folder: folder,
- }
-
- return req, true
-}
-
-func TranslateToListRequest(namespace, action, kind string) (*authlib.ListRequest, bool) {
- translation, ok := resourceTranslations[kind]
-
- if !ok {
- return nil, false
- }
-
- // FIXME: support different verbs
- req := &authlib.ListRequest{
- Namespace: namespace,
- Group: translation.group,
- Resource: translation.resource,
- }
-
- return req, true
-}
-
-func TranslateToGroupResource(kind string) string {
- translation, ok := resourceTranslations[kind]
- if !ok {
- return ""
- }
- return common.FormatGroupResource(translation.group, translation.resource, "")
-}
-
-func TranslateBasicRole(name string) string {
- return basicRolesTranslations[name]
-}
diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod
index de0fa04b0e5..edebf0d0f81 100644
--- a/pkg/storage/unified/apistore/go.mod
+++ b/pkg/storage/unified/apistore/go.mod
@@ -60,12 +60,10 @@ require (
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.3.1 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
- github.com/Masterminds/squirrel v1.5.4 // indirect
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // indirect
- github.com/Yiling-J/theine-go v0.6.1 // indirect
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
@@ -134,7 +132,6 @@ require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dennwc/varint v1.0.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
- github.com/distribution/reference v0.6.0 // indirect
github.com/dlmiddlecote/sqlstats v1.0.2 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 // indirect
@@ -142,10 +139,8 @@ require (
github.com/dolthub/go-mysql-server v0.19.1-0.20250206012855-c216e59c21a7 // indirect
github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect
github.com/dolthub/vitess v0.0.0-20250123002143-3b45b8cacbfa // indirect
- github.com/dustin/go-humanize v1.0.1 // indirect
github.com/elazarl/goproxy v1.7.1 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
- github.com/emirpasic/gods v1.18.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/fatih/color v1.18.0 // indirect
@@ -175,7 +170,6 @@ require (
github.com/go-openapi/validate v0.24.0 // indirect
github.com/go-sql-driver/mysql v1.9.2 // indirect
github.com/go-stack/stack v1.8.1 // indirect
- github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.4 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect
@@ -219,7 +213,6 @@ require (
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/grafana/sqlds/v4 v4.1.3 // indirect
- github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
@@ -238,10 +231,6 @@ require (
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
- github.com/jackc/pgpassfile v1.0.0 // indirect
- github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
- github.com/jackc/pgx/v5 v5.7.5 // indirect
- github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jessevdk/go-flags v1.5.0 // indirect
github.com/jhump/protoreflect v1.15.1 // indirect
github.com/jmespath-community/go-jmespath v1.1.1 // indirect
@@ -255,8 +244,6 @@ require (
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
- github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
- github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lestrrat-go/strftime v1.0.4 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/magefile/mage v1.15.0 // indirect
@@ -270,7 +257,6 @@ require (
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/mdlayher/vsock v1.2.1 // indirect
- github.com/mfridman/interpolate v0.0.2 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect
@@ -291,31 +277,23 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
- github.com/natefinch/wrap v0.2.0 // indirect
- github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
- github.com/oklog/ulid/v2 v2.1.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/open-feature/go-sdk v1.14.1 // indirect
github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.3 // indirect
github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5 // indirect
- github.com/openfga/api/proto v0.0.0-20250127102726-f9709139a369 // indirect
- github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20250220223040-ed0cfba54336 // indirect
- github.com/openfga/openfga v1.8.13 // indirect
github.com/opentracing-contrib/go-stdlib v1.0.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
- github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
- github.com/pressly/goose/v3 v3.24.3 // indirect
github.com/prometheus/alertmanager v0.27.0 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
@@ -325,27 +303,20 @@ require (
github.com/prometheus/procfs v0.16.1 // indirect
github.com/prometheus/prometheus v0.301.0 // indirect
github.com/redis/go-redis/v9 v9.7.3 // indirect
- github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
- github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
- github.com/sethvargo/go-retry v0.3.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
- github.com/sourcegraph/conc v0.3.0 // indirect
- github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
- github.com/spf13/viper v1.20.1 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
- github.com/subosito/gotenv v1.6.0 // indirect
github.com/tetratelabs/wazero v1.8.2 // indirect
github.com/tjhop/slog-gokit v0.1.3 // indirect
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
@@ -381,7 +352,6 @@ require (
go.opentelemetry.io/otel/trace v1.36.0 // indirect
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
- go.uber.org/mock v0.5.2 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
@@ -418,10 +388,6 @@ require (
k8s.io/kube-aggregator v0.32.0 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
- modernc.org/libc v1.65.0 // indirect
- modernc.org/mathutil v1.7.1 // indirect
- modernc.org/memory v1.10.0 // indirect
- modernc.org/sqlite v1.37.0 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect
diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum
index 5f99813d33d..a6306a104b2 100644
--- a/pkg/storage/unified/apistore/go.sum
+++ b/pkg/storage/unified/apistore/go.sum
@@ -680,11 +680,7 @@ github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7r
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
-github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
-github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
-github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
-github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
@@ -697,8 +693,6 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f h1:HR5nRmUQgXrwqZOwZ2DAc/aCi3Bu3xENpspW935vxu0=
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f/go.mod h1:f3HiCrHjHBdcm6E83vGaXh1KomZMA2P6aeo3hKx/wg0=
-github.com/Yiling-J/theine-go v0.6.1 h1:njE/rBBviU/Sq2G7PJKdLdwXg8j1azvZQulIjmshD+o=
-github.com/Yiling-J/theine-go v0.6.1/go.mod h1:08QpMa5JZ2pKN+UJCRrCasWYO1IKCdl54Xa836rpmDU=
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
@@ -781,7 +775,6 @@ github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPn
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 h1:6df1vn4bBlDDo4tARvBm7l6KA9iVMnE3NWizDeWSrps=
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3/go.mod h1:CIWtjkly68+yqLPbvwwR/fjNJA/idrtULjZWh2v1ys0=
-github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -923,18 +916,12 @@ github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgz
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dhui/dktest v0.3.0/go.mod h1:cyzIUfGsBEbZ6BT7tnXqAShHSXCZhSNmFl70sZ7c1yc=
-github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
-github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlmiddlecote/sqlstats v1.0.2 h1:gSU11YN23D/iY50A2zVYwgXgy072khatTsIW6UPjUtI=
github.com/dlmiddlecote/sqlstats v1.0.2/go.mod h1:0CWaIh/Th+z2aI6Q9Jpfg/o21zmGxWhbByHgQSCUQvY=
github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v0.7.3-0.20190817195342-4760db040282/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
-github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
-github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
-github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
-github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
@@ -960,8 +947,6 @@ github.com/elazarl/goproxy v1.7.1 h1:1P7LPSxbqtNxusFnXclj6O56pjfq1xOQZ6a0mwwKUlY
github.com/elazarl/goproxy v1.7.1/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
-github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
-github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -1078,8 +1063,6 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
-github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
-github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
@@ -1316,8 +1299,6 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
-github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
-github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
@@ -1332,8 +1313,6 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9
github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg=
github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
-github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
-github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I=
github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI=
@@ -1361,15 +1340,7 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
-github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
-github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
-github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
-github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
-github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
-github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
-github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
-github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
@@ -1443,10 +1414,6 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
-github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
-github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
-github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
-github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8=
@@ -1490,8 +1457,6 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
-github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
-github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
@@ -1517,8 +1482,6 @@ github.com/mithrandie/go-text v1.6.0 h1:8gOXTMPbMY8DJbKMTv8kHhADcJlDWXqS/YQH4SyW
github.com/mithrandie/go-text v1.6.0/go.mod h1:xCgj1xiNbI/d4xA9sLVvXkjh5B2tNx2ZT2/3rpmh8to=
github.com/mithrandie/ternary v1.1.1 h1:k/joD6UGVYxHixYmSR8EGgDFNONBMqyD373xT4QRdC4=
github.com/mithrandie/ternary v1.1.1/go.mod h1:0D9Ba3+09K2TdSZO7/bFCC0GjSXetCvYuYq0u8FY/1g=
-github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
-github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/mocktools/go-smtp-mock/v2 v2.3.1 h1:wq75NDSsOy5oHo/gEQQT0fRRaYKRqr1IdkjhIPXxagM=
@@ -1543,10 +1506,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA=
-github.com/natefinch/wrap v0.2.0 h1:IXzc/pw5KqxJv55gV0lSOcKHYuEZPGbQrOOXr/bamRk=
-github.com/natefinch/wrap v0.2.0/go.mod h1:6gMHlAl12DwYEfKP3TkuykYUfLSEAvHw67itm4/KAS8=
-github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
-github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
@@ -1555,8 +1514,6 @@ github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
-github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -1574,17 +1531,7 @@ github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.3/go.mod h
github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5 h1:ZdqlGnNwhWf3luhBQlIpbglvcCzjkcuEgOEhYhr5Emc=
github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5/go.mod h1:jrD4UG3ZCzuwImKHlyuIN2iWeYjlOX5+zJ/sX45efuE=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
-github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
-github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
-github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
-github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
-github.com/openfga/api/proto v0.0.0-20250127102726-f9709139a369 h1:wEsCZ4oBuu8LfEJ3VXbveXO8uEhCthrxA40WSvxO044=
-github.com/openfga/api/proto v0.0.0-20250127102726-f9709139a369/go.mod h1:m74TNgnAAIJ03gfHcx+xaRWnr+IbQy3y/AVNwwCFrC0=
-github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20250220223040-ed0cfba54336 h1:pYuYanFfgYrvDoSu/nnThT9P60mw5Yx7PMEI7FYychM=
-github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20250220223040-ed0cfba54336/go.mod h1:IWRgDIekw3UGSWINwmCALHpMmn6NEJzz6e7KZGm+xQ4=
-github.com/openfga/openfga v1.8.13 h1:ROURkotKhbmtyBX3188+cNElN8AOZmTl0CMkxUqwawo=
-github.com/openfga/openfga v1.8.13/go.mod h1:h1VGcVW81eY1YyDtFx5+gxxAIEhIiOGR9SRGgs/X/k8=
github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w=
github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@@ -1596,9 +1543,6 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
-github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
-github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
-github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
@@ -1622,8 +1566,6 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/pressly/goose/v3 v3.24.3 h1:DSWWNwwggVUsYZ0X2VitiAa9sKuqtBfe+Jr9zFGwWlM=
-github.com/pressly/goose/v3 v3.24.3/go.mod h1:v9zYL4xdViLHCUUJh/mhjnm6JrK7Eul8AS93IxiZM4E=
github.com/prometheus/alertmanager v0.27.0 h1:V6nTa2J5V4s8TG4C4HtrBP/WNSebCCTYGGv4qecA/+I=
github.com/prometheus/alertmanager v0.27.0/go.mod h1:8Ia/R3urPmbzJ8OsdvmZvIprDwvwmYCmUbwBL+jlPOE=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -1676,8 +1618,6 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
-github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
-github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -1694,13 +1634,9 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
-github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
-github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
-github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
@@ -1722,22 +1658,16 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
-github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
-github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
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.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
-github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
-github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
-github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
@@ -1763,8 +1693,6 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
-github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA=
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0=
github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4=
@@ -1888,18 +1816,12 @@ go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
-go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
-go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
-go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
-go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
-go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
gocloud.dev v0.40.0 h1:f8LgP+4WDqOG/RXoUcyLpeIAGOcAbZrZbDQCUee10ng=
@@ -2182,7 +2104,6 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/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-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -2283,7 +2204,6 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -2449,7 +2369,6 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
@@ -2668,7 +2587,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -2707,21 +2625,13 @@ lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl
modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
-modernc.org/cc/v4 v4.26.0 h1:QMYvbVduUGH0rrO+5mqF/PSPPRZNpRtg2CLELy7vUpA=
-modernc.org/cc/v4 v4.26.0/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws=
modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=
-modernc.org/ccgo/v4 v4.26.0 h1:gVzXaDzGeBYJ2uXTOpR8FR7OlksDOe9jxnjhIKCsiTc=
-modernc.org/ccgo/v4 v4.26.0/go.mod h1:Sem8f7TFUtVXkG2fiaChQtyyfkqhJBg/zjEJBkmuAVY=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
-modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8=
-modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
-modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
-modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=
@@ -2730,35 +2640,19 @@ modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=
modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s=
-modernc.org/libc v1.65.0 h1:e183gLDnAp9VJh6gWKdTy0CThL9Pt7MfcR/0bgb7Y1Y=
-modernc.org/libc v1.65.0/go.mod h1:7m9VzGq7APssBTydds2zBcxGREwvIGpuUBaKTXdm2Qs=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
-modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
-modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
-modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4=
-modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
-modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
-modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
-modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
-modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=
-modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
-modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
-modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
-modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
-modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
-modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
--
2.51.0