From 6bdc91e71db79b6e4c9f6c9bef706d1a37a8c18c Mon Sep 17 00:00:00 2001 From: Kush Sharma Date: Sat, 2 Sep 2023 16:16:02 +0530 Subject: [PATCH] fixed postgres repositories to use namespace id Signed-off-by: Kush Sharma --- Makefile | 4 + cli/server.go | 2 +- domain/namespace.go | 12 ++ go.mod | 21 ++- go.sum | 53 +++++-- internal/server/config.go | 11 +- internal/server/server.go | 31 +++- internal/server/services.go | 14 +- .../store/postgres/activity_repository.go | 69 +++++---- .../postgres/activity_repository_test.go | 6 +- internal/store/postgres/appeal_repository.go | 138 +++++++++--------- .../store/postgres/appeal_repository_test.go | 8 +- .../store/postgres/approval_repository.go | 99 +++++++------ .../postgres/approval_repository_test.go | 10 +- internal/store/postgres/grant_repository.go | 127 ++++++++-------- .../store/postgres/grant_repository_test.go | 10 +- ...table_and_add_namespace_in_tables.down.sql | 15 +- ...e_table_and_add_namespace_in_tables.up.sql | 48 ++---- ...ble_row_level_security_all_tables.down.sql | 6 +- ...nable_row_level_security_all_tables.up.sql | 10 +- internal/store/postgres/model/activity.go | 2 +- internal/store/postgres/model/appeal.go | 1 + internal/store/postgres/model/approval.go | 1 + internal/store/postgres/model/approver.go | 9 +- internal/store/postgres/model/grant.go | 1 + internal/store/postgres/model/namespace.go | 65 +++++++++ internal/store/postgres/model/policy.go | 7 +- internal/store/postgres/model/provider.go | 17 ++- internal/store/postgres/model/resource.go | 10 +- .../store/postgres/namespace_repository.go | 79 ++++++++++ internal/store/postgres/policy_repository.go | 15 +- .../store/postgres/policy_repository_test.go | 2 +- .../store/postgres/provider_repository.go | 37 +++-- .../postgres/provider_repository_test.go | 6 +- .../store/postgres/resource_repository.go | 100 +++++++------ .../postgres/resource_repository_test.go | 4 +- internal/store/postgres/store.go | 35 +++++ pkg/auth/frontier.go | 53 +++++++ 38 files changed, 747 insertions(+), 391 deletions(-) create mode 100644 domain/namespace.go create mode 100644 internal/store/postgres/model/namespace.go create mode 100644 internal/store/postgres/namespace_repository.go create mode 100644 pkg/auth/frontier.go diff --git a/Makefile b/Makefile index 58edec0af..680cb75be 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,10 @@ lint: ## Lint checker @echo "Running lint checks using golangci-lint..." @golangci-lint run +lintf: ## Lint checker + @echo "Running lint checks using golangci-lint..." + @golangci-lint run --fix + clean: tidy ## Clean the build artifacts @echo "Cleaning up build directories..." @rm -rf $coverage.out ${BUILD_DIR} diff --git a/cli/server.go b/cli/server.go index 90f6533f5..5f0fe35e4 100644 --- a/cli/server.go +++ b/cli/server.go @@ -57,7 +57,7 @@ func migrateCommand() *cobra.Command { if err != nil { return err } - return server.Migrate(&cfg) + return server.Migrate(cmd.Context(), &cfg) }, } diff --git a/domain/namespace.go b/domain/namespace.go new file mode 100644 index 000000000..48cf82309 --- /dev/null +++ b/domain/namespace.go @@ -0,0 +1,12 @@ +package domain + +import "time" + +type Namespace struct { + ID string + Name string + State string + Metadata map[string]interface{} + CreatedAt time.Time + UpdatedAt time.Time +} diff --git a/go.mod b/go.mod index 79bbd1e12..81c6fa89d 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 github.com/imdario/mergo v0.3.12 + github.com/lestrrat-go/jwx/v2 v2.0.12 github.com/lib/pq v1.10.0 github.com/mcuadros/go-defaults v1.2.0 github.com/mcuadros/go-lookup v0.0.0-20200831155250-80f87a4fa5ee @@ -30,7 +31,7 @@ require ( github.com/sergi/go-diff v1.0.0 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.4 github.com/uptrace/opentelemetry-go-extra/otelgorm v0.1.17 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.37.0 @@ -38,7 +39,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.2 go.opentelemetry.io/otel/sdk v1.14.0 - golang.org/x/net v0.8.0 + golang.org/x/net v0.10.0 golang.org/x/oauth2 v0.1.0 golang.org/x/sync v0.1.0 google.golang.org/api v0.84.0 @@ -68,6 +69,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dlclark/regexp2 v1.2.0 // indirect github.com/docker/cli v23.0.1+incompatible // indirect github.com/docker/docker v23.0.1+incompatible // indirect @@ -80,6 +82,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.13.0 // indirect github.com/go-playground/universal-translator v0.17.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -103,6 +106,11 @@ require ( github.com/jmoiron/sqlx v1.3.5 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/leodido/go-urn v1.2.0 // indirect + github.com/lestrrat-go/blackmagic v1.0.1 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.4 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mattn/go-colorable v0.1.12 // indirect @@ -124,6 +132,7 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/schollz/progressbar/v3 v3.8.5 // indirect + github.com/segmentio/asm v1.2.0 // indirect github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -146,11 +155,11 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.7.0 // indirect + golang.org/x/crypto v0.12.0 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/term v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/term v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect golang.org/x/tools v0.7.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 129d11b65..348b9cf07 100644 --- a/go.sum +++ b/go.sum @@ -411,6 +411,9 @@ github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8= github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= @@ -574,6 +577,8 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= @@ -918,6 +923,19 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= +github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= +github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx/v2 v2.0.12 h1:3d589+5w/b9b7S3DneICPW16AqTyYXB7VRjgluSDWeA= +github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G/0y2qUjAQuQ= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -1201,6 +1219,8 @@ github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= @@ -1275,8 +1295,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -1447,8 +1467,8 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1497,6 +1517,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1577,8 +1598,9 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1764,8 +1786,10 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1773,8 +1797,10 @@ golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4 golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1786,8 +1812,10 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1877,6 +1905,7 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/server/config.go b/internal/server/config.go index 93f0862b9..62b32f637 100644 --- a/internal/server/config.go +++ b/internal/server/config.go @@ -19,9 +19,10 @@ type DefaultAuth struct { } type Auth struct { - Provider string `mapstructure:"provider" default:"default"` - Default DefaultAuth `mapstructure:"default"` - OIDC auth.OIDCAuth `mapstructure:"oidc"` + Provider string `mapstructure:"provider" default:"default"` + Default DefaultAuth `mapstructure:"default"` + OIDC auth.OIDCAuth `mapstructure:"oidc"` + Frontier auth.FrontierConfig `mapstructure:"frontier"` } type Jobs struct { @@ -87,5 +88,9 @@ func LoadConfig(serverConfigFileFromFlag string) (Config, error) { cfg.Auth.Default.HeaderKey = cfg.AuthenticatedUserHeaderKey } + // fail if encryption secret key is not set + if cfg.EncryptionSecretKeyKey == "" { + fmt.Println("WARNING: encryption_secret_key is not set") + } return cfg, nil } diff --git a/internal/server/server.go b/internal/server/server.go index 68b943508..9eb352e38 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -8,6 +8,9 @@ import ( "strings" "time" + "github.com/google/uuid" + "github.com/raystack/guardian/domain" + "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -124,6 +127,7 @@ func RunServer(config *Config) error { ), grpc_logrus.UnaryServerInterceptor(logrusEntry), authInterceptor, + auth.FrontierJWTInterceptor(config.Auth.Frontier), withLogrusContext(), otelgrpc.UnaryServerInterceptor(), )), @@ -192,7 +196,7 @@ func RunServer(config *Config) error { }) baseMux.Handle("/api/", http.StripPrefix("/api", gwmux)) - logger.Info(fmt.Sprintf("server running on %s", address)) + logger.Info(fmt.Sprintf("server running on %s(rest) and %s(grpc)", address, grpcAddress)) return mux.Serve( runtimeCtx, @@ -208,7 +212,7 @@ func RunServer(config *Config) error { } // Migrate runs the schema migration scripts -func Migrate(c *Config) error { +func Migrate(ctx context.Context, c *Config) error { store, err := getStore(c) if err != nil { return err @@ -217,11 +221,30 @@ func Migrate(c *Config) error { sqldb, _ := store.DB().DB() auditRepository := audit_repos.NewPostgresRepository(sqldb) - if err := auditRepository.Init(context.Background()); err != nil { + if err := auditRepository.Init(ctx); err != nil { return fmt.Errorf("initializing audit repository: %w", err) } - return store.Migrate() + if err := store.Migrate(); err != nil { + return fmt.Errorf("migrating database: %w", err) + } + namespaceRepo := postgres.NewNamespaceRepository(store) + if _, err := namespaceRepo.GetOne(ctx, uuid.Nil.String()); err != nil { + fmt.Println("> migrating default namespace") + if err := namespaceRepo.BulkUpsert(ctx, []*domain.Namespace{ + { + ID: uuid.Nil.String(), + Name: "default", + State: "active", + }, + }); err != nil { + return fmt.Errorf("creating default namespace failed: %w", err) + } + fmt.Println("> default namespace migrated successfully") + } + + fmt.Println("> migration completed successfully") + return nil } func getStore(c *Config) (*postgres.Store, error) { diff --git a/internal/server/services.go b/internal/server/services.go index 1ccfa2aab..4c019720e 100644 --- a/internal/server/services.go +++ b/internal/server/services.go @@ -94,13 +94,13 @@ func InitServices(deps ServiceDeps) (*Services, error) { actorExtractor, ) - activityRepository := postgres.NewActivityRepository(store.DB()) - providerRepository := postgres.NewProviderRepository(store.DB()) - policyRepository := postgres.NewPolicyRepository(store.DB()) - resourceRepository := postgres.NewResourceRepository(store.DB()) - appealRepository := postgres.NewAppealRepository(store.DB()) - approvalRepository := postgres.NewApprovalRepository(store.DB()) - grantRepository := postgres.NewGrantRepository(store.DB()) + activityRepository := postgres.NewActivityRepository(store) + providerRepository := postgres.NewProviderRepository(store) + policyRepository := postgres.NewPolicyRepository(store) + resourceRepository := postgres.NewResourceRepository(store) + appealRepository := postgres.NewAppealRepository(store) + approvalRepository := postgres.NewApprovalRepository(store) + grantRepository := postgres.NewGrantRepository(store) providerClients := []provider.Client{ bigquery.NewProvider(domain.ProviderTypeBigQuery, deps.Crypto, deps.Logger), diff --git a/internal/store/postgres/activity_repository.go b/internal/store/postgres/activity_repository.go index 8c34dd43e..1c1c3ba61 100644 --- a/internal/store/postgres/activity_repository.go +++ b/internal/store/postgres/activity_repository.go @@ -5,43 +5,47 @@ import ( "errors" "fmt" + "gorm.io/gorm/clause" + "github.com/raystack/guardian/core/activity" "github.com/raystack/guardian/domain" "github.com/raystack/guardian/internal/store/postgres/model" "gorm.io/gorm" - "gorm.io/gorm/clause" ) type ActivityRepository struct { - db *gorm.DB + store *Store } -func NewActivityRepository(db *gorm.DB) *ActivityRepository { - return &ActivityRepository{db} +func NewActivityRepository(store *Store) *ActivityRepository { + return &ActivityRepository{ + store: store, + } } func (r *ActivityRepository) Find(ctx context.Context, filter domain.ListProviderActivitiesFilter) ([]*domain.Activity, error) { var activities []*model.Activity - db := r.db.WithContext(ctx) - if filter.ProviderIDs != nil { - db = db.Where(`"provider_id" IN ?`, filter.ProviderIDs) - } - if filter.ResourceIDs != nil { - db = db.Where(`"resource_id" IN ?`, filter.ResourceIDs) - } - if filter.AccountIDs != nil { - db = db.Where(`"account_id" IN ?`, filter.AccountIDs) - } - if filter.Types != nil { - db = db.Where(`"type" IN ?`, filter.Types) - } - if filter.TimestampGte != nil { - db = db.Where(`"timestamp" >= ?`, *filter.TimestampGte) - } - if filter.TimestampLte != nil { - db = db.Where(`"timestamp" <= ?`, *filter.TimestampLte) - } - if err := db.Find(&activities).Error; err != nil { + if err := r.store.Tx(ctx, func(tx *gorm.DB) error { + if filter.ProviderIDs != nil { + tx = tx.Where(`"provider_id" IN ?`, filter.ProviderIDs) + } + if filter.ResourceIDs != nil { + tx = tx.Where(`"resource_id" IN ?`, filter.ResourceIDs) + } + if filter.AccountIDs != nil { + tx = tx.Where(`"account_id" IN ?`, filter.AccountIDs) + } + if filter.Types != nil { + tx = tx.Where(`"type" IN ?`, filter.Types) + } + if filter.TimestampGte != nil { + tx = tx.Where(`"timestamp" >= ?`, *filter.TimestampGte) + } + if filter.TimestampLte != nil { + tx = tx.Where(`"timestamp" <= ?`, *filter.TimestampLte) + } + return tx.Find(&activities).Error + }); err != nil { return nil, err } @@ -58,12 +62,12 @@ func (r *ActivityRepository) Find(ctx context.Context, filter domain.ListProvide func (r *ActivityRepository) GetOne(ctx context.Context, id string) (*domain.Activity, error) { var m model.Activity - if err := r.db. - WithContext(ctx). - Joins("Provider"). - Joins("Resource"). - Where(`"activities"."id" = ?`, id). - First(&m).Error; err != nil { + if err := r.store.Tx(ctx, func(tx *gorm.DB) error { + return tx.Joins("Provider"). + Joins("Resource"). + Where(`"activities"."id" = ?`, id). + First(&m).Error + }); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, activity.ErrNotFound } @@ -91,6 +95,7 @@ func (r *ActivityRepository) BulkUpsert(ctx context.Context, activities []*domai if err := activityModels[i].FromDomain(a); err != nil { return fmt.Errorf("failed to convert domain to model: %w", err) } + activityModels[i].NamespaceID = namespaceFromContext(ctx) // use single resource reference for activities with same resource if r := activityModels[i].Resource; r != nil { @@ -103,10 +108,11 @@ func (r *ActivityRepository) BulkUpsert(ctx context.Context, activities []*domai } } - return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + return r.store.Tx(ctx, func(tx *gorm.DB) error { // upsert resources separately to avoid resource upsertion duplicate issue var resources []*model.Resource for _, r := range uniqueResourcesMap { + r.NamespaceID = namespaceFromContext(ctx) resources = append(resources, r) } if len(resources) > 0 { @@ -124,6 +130,7 @@ func (r *ActivityRepository) BulkUpsert(ctx context.Context, activities []*domai if err := tx.Omit("Resource"). Clauses(clause.OnConflict{ Columns: []clause.Column{ + {Name: "namespace_id"}, {Name: "provider_id"}, {Name: "provider_activity_id"}, }, diff --git a/internal/store/postgres/activity_repository_test.go b/internal/store/postgres/activity_repository_test.go index 4c3469df7..82b9c3231 100644 --- a/internal/store/postgres/activity_repository_test.go +++ b/internal/store/postgres/activity_repository_test.go @@ -42,9 +42,9 @@ func (s *ActivityRepositoryTestSuite) SetupSuite() { s.T().Fatal(err) } s.store = store - s.repository = postgres.NewActivityRepository(store.DB()) - s.providerRepository = postgres.NewProviderRepository(store.DB()) - s.resourceRepository = postgres.NewResourceRepository(store.DB()) + s.repository = postgres.NewActivityRepository(store) + s.providerRepository = postgres.NewProviderRepository(store) + s.resourceRepository = postgres.NewResourceRepository(store) s.T().Cleanup(func() { db, err := s.store.DB().DB() diff --git a/internal/store/postgres/appeal_repository.go b/internal/store/postgres/appeal_repository.go index 08dbeadde..c9e69068e 100644 --- a/internal/store/postgres/appeal_repository.go +++ b/internal/store/postgres/appeal_repository.go @@ -24,27 +24,30 @@ var ( // AppealRepository talks to the store to read or insert data type AppealRepository struct { - db *gorm.DB + store *Store } // NewAppealRepository returns repository struct -func NewAppealRepository(db *gorm.DB) *AppealRepository { - return &AppealRepository{db} +func NewAppealRepository(store *Store) *AppealRepository { + return &AppealRepository{ + store: store, + } } // GetByID returns appeal record by id along with the approvals and the approvers func (r *AppealRepository) GetByID(ctx context.Context, id string) (*domain.Appeal, error) { m := new(model.Appeal) - if err := r.db. - WithContext(ctx). - Preload("Approvals", func(db *gorm.DB) *gorm.DB { - return db.Order("Approvals.index ASC") - }). - Preload("Approvals.Approvers"). - Preload("Resource"). - Preload("Grant"). - First(&m, "id = ?", id). - Error; err != nil { + if err := r.store.Tx(ctx, func(tx *gorm.DB) error { + return tx. + Preload("Approvals", func(db *gorm.DB) *gorm.DB { + return db.Order("Approvals.index ASC") + }). + Preload("Approvals.Approvers"). + Preload("Resource"). + Preload("Grant"). + First(&m, "id = ?", id). + Error + }); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, appeal.ErrAppealNotFound } @@ -63,59 +66,62 @@ func (r *AppealRepository) Find(ctx context.Context, filters *domain.ListAppeals if err := utils.ValidateStruct(filters); err != nil { return nil, err } + var models []*model.Appeal - db := r.db.WithContext(ctx) - if filters.CreatedBy != "" { - db = db.Where(`"appeals"."created_by" = ?`, filters.CreatedBy) - } - accounts := make([]string, 0) - if filters.AccountID != "" { - accounts = append(accounts, filters.AccountID) - } - if filters.AccountIDs != nil { - accounts = append(accounts, filters.AccountIDs...) - } - if len(accounts) > 0 { - db = db.Where(`"appeals"."account_id" IN ?`, accounts) - } - if filters.Statuses != nil { - db = db.Where(`"appeals"."status" IN ?`, filters.Statuses) - } - if filters.ResourceID != "" { - db = db.Where(`"appeals"."resource_id" = ?`, filters.ResourceID) - } - if filters.Role != "" { - db = db.Where(`"appeals"."role" = ?`, filters.Role) - } - if !filters.ExpirationDateLessThan.IsZero() { - db = db.Where(`"options" -> 'expiration_date' < ?`, filters.ExpirationDateLessThan) - } - if !filters.ExpirationDateGreaterThan.IsZero() { - db = db.Where(`"options" -> 'expiration_date' > ?`, filters.ExpirationDateGreaterThan) - } - if filters.OrderBy != nil { - db = addOrderByClause(db, filters.OrderBy, addOrderByClauseOptions{ - statusColumnName: `"appeals"."status"`, - statusesOrder: AppealStatusDefaultSort, - }) - } + err := r.store.Tx(ctx, func(tx *gorm.DB) error { + db := tx + if filters.CreatedBy != "" { + db = db.Where(`"appeals"."created_by" = ?`, filters.CreatedBy) + } + accounts := make([]string, 0) + if filters.AccountID != "" { + accounts = append(accounts, filters.AccountID) + } + if filters.AccountIDs != nil { + accounts = append(accounts, filters.AccountIDs...) + } + if len(accounts) > 0 { + db = db.Where(`"appeals"."account_id" IN ?`, accounts) + } + if filters.Statuses != nil { + db = db.Where(`"appeals"."status" IN ?`, filters.Statuses) + } + if filters.ResourceID != "" { + db = db.Where(`"appeals"."resource_id" = ?`, filters.ResourceID) + } + if filters.Role != "" { + db = db.Where(`"appeals"."role" = ?`, filters.Role) + } + if !filters.ExpirationDateLessThan.IsZero() { + db = db.Where(`"options" -> 'expiration_date' < ?`, filters.ExpirationDateLessThan) + } + if !filters.ExpirationDateGreaterThan.IsZero() { + db = db.Where(`"options" -> 'expiration_date' > ?`, filters.ExpirationDateGreaterThan) + } + if filters.OrderBy != nil { + db = addOrderByClause(db, filters.OrderBy, addOrderByClauseOptions{ + statusColumnName: `"appeals"."status"`, + statusesOrder: AppealStatusDefaultSort, + }) + } - db = db.Joins("Resource") - if filters.ProviderTypes != nil { - db = db.Where(`"Resource"."provider_type" IN ?`, filters.ProviderTypes) - } - if filters.ProviderURNs != nil { - db = db.Where(`"Resource"."provider_urn" IN ?`, filters.ProviderURNs) - } - if filters.ResourceTypes != nil { - db = db.Where(`"Resource"."type" IN ?`, filters.ResourceTypes) - } - if filters.ResourceURNs != nil { - db = db.Where(`"Resource"."urn" IN ?`, filters.ResourceURNs) - } + db = db.Joins("Resource") + if filters.ProviderTypes != nil { + db = db.Where(`"Resource"."provider_type" IN ?`, filters.ProviderTypes) + } + if filters.ProviderURNs != nil { + db = db.Where(`"Resource"."provider_urn" IN ?`, filters.ProviderURNs) + } + if filters.ResourceTypes != nil { + db = db.Where(`"Resource"."type" IN ?`, filters.ResourceTypes) + } + if filters.ResourceURNs != nil { + db = db.Where(`"Resource"."urn" IN ?`, filters.ResourceURNs) + } - var models []*model.Appeal - if err := db.Joins("Grant").Find(&models).Error; err != nil { + return db.Joins("Grant").Find(&models).Error + }) + if err != nil { return nil, err } @@ -140,10 +146,11 @@ func (r *AppealRepository) BulkUpsert(ctx context.Context, appeals []*domain.App if err := m.FromDomain(a); err != nil { return err } + m.NamespaceID = namespaceFromContext(ctx) models = append(models, m) } - return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + return r.store.Tx(ctx, func(tx *gorm.DB) error { if err := tx. Clauses(clause.OnConflict{UpdateAll: true}). Create(models). @@ -170,8 +177,9 @@ func (r *AppealRepository) Update(ctx context.Context, a *domain.Appeal) error { if err := m.FromDomain(a); err != nil { return err } + m.NamespaceID = namespaceFromContext(ctx) - return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + return r.store.Tx(ctx, func(tx *gorm.DB) error { if err := tx.Omit("Approvals.Approvers").Session(&gorm.Session{FullSaveAssociations: true}).Save(&m).Error; err != nil { return err } diff --git a/internal/store/postgres/appeal_repository_test.go b/internal/store/postgres/appeal_repository_test.go index 8862c8620..3b1122960 100644 --- a/internal/store/postgres/appeal_repository_test.go +++ b/internal/store/postgres/appeal_repository_test.go @@ -38,13 +38,13 @@ func (s *AppealRepositoryTestSuite) SetupSuite() { ctx := context.Background() - s.repository = postgres.NewAppealRepository(s.store.DB()) + s.repository = postgres.NewAppealRepository(s.store) s.dummyPolicy = &domain.Policy{ ID: "policy_test", Version: 1, } - policyRepository := postgres.NewPolicyRepository(s.store.DB()) + policyRepository := postgres.NewPolicyRepository(s.store) err = policyRepository.Create(ctx, s.dummyPolicy) s.Require().NoError(err) @@ -63,7 +63,7 @@ func (s *AppealRepositoryTestSuite) SetupSuite() { }, }, } - providerRepository := postgres.NewProviderRepository(s.store.DB()) + providerRepository := postgres.NewProviderRepository(s.store) err = providerRepository.Create(ctx, s.dummyProvider) s.Require().NoError(err) @@ -74,7 +74,7 @@ func (s *AppealRepositoryTestSuite) SetupSuite() { URN: "resource_urn_test", Name: "resource_name_test", } - resourceRepository := postgres.NewResourceRepository(s.store.DB()) + resourceRepository := postgres.NewResourceRepository(s.store) err = resourceRepository.BulkUpsert(ctx, []*domain.Resource{s.dummyResource}) s.Require().NoError(err) } diff --git a/internal/store/postgres/approval_repository.go b/internal/store/postgres/approval_repository.go index 96cf1fd62..e57363b13 100644 --- a/internal/store/postgres/approval_repository.go +++ b/internal/store/postgres/approval_repository.go @@ -22,10 +22,10 @@ var ( ) type ApprovalRepository struct { - db *gorm.DB + store *Store } -func NewApprovalRepository(db *gorm.DB) *ApprovalRepository { +func NewApprovalRepository(db *Store) *ApprovalRepository { return &ApprovalRepository{db} } @@ -34,49 +34,50 @@ func (r *ApprovalRepository) ListApprovals(ctx context.Context, conditions *doma return nil, err } - records := []*domain.Approval{} - - db := r.db.WithContext(ctx) - db = db.Preload("Appeal.Resource") - db = db.Joins("Appeal") - db = db.Joins(`JOIN "approvers" ON "approvals"."id" = "approvers"."approval_id"`) + var models []*model.Approval + err := r.store.Tx(ctx, func(tx *gorm.DB) error { + tx = tx.Preload("Appeal.Resource") + tx = tx.Joins("Appeal") + tx = tx.Joins(`JOIN "approvers" ON "approvals"."id" = "approvers"."approval_id"`) - if conditions.CreatedBy != "" { - db = db.Where(`"approvers"."email" = ?`, conditions.CreatedBy) - } - if conditions.Statuses != nil { - db = db.Where(`"approvals"."status" IN ?`, conditions.Statuses) - } - if conditions.AccountID != "" { - db = db.Where(`"Appeal"."account_id" = ?`, conditions.AccountID) - } + if conditions.CreatedBy != "" { + tx = tx.Where(`"approvers"."email" = ?`, conditions.CreatedBy) + } + if conditions.Statuses != nil { + tx = tx.Where(`"approvals"."status" IN ?`, conditions.Statuses) + } + if conditions.AccountID != "" { + tx = tx.Where(`"Appeal"."account_id" = ?`, conditions.AccountID) + } - if len(conditions.AppealStatuses) == 0 { - db = db.Where(`"Appeal"."status" != ?`, domain.AppealStatusCanceled) - } else { - db = db.Where(`"Appeal"."status" IN ?`, conditions.AppealStatuses) - } + if len(conditions.AppealStatuses) == 0 { + tx = tx.Where(`"Appeal"."status" != ?`, domain.AppealStatusCanceled) + } else { + tx = tx.Where(`"Appeal"."status" IN ?`, conditions.AppealStatuses) + } - if conditions.OrderBy != nil { - db = addOrderByClause(db, conditions.OrderBy, addOrderByClauseOptions{ - statusColumnName: `"approvals"."status"`, - statusesOrder: AppealStatusDefaultSort, - }) - } + if conditions.OrderBy != nil { + tx = addOrderByClause(tx, conditions.OrderBy, addOrderByClauseOptions{ + statusColumnName: `"approvals"."status"`, + statusesOrder: AppealStatusDefaultSort, + }) + } - if conditions.Size > 0 { - db = db.Limit(conditions.Size) - } + if conditions.Size > 0 { + tx = tx.Limit(conditions.Size) + } - if conditions.Offset > 0 { - db = db.Offset(conditions.Offset) - } + if conditions.Offset > 0 { + tx = tx.Offset(conditions.Offset) + } - var models []*model.Approval - if err := db.Find(&models).Error; err != nil { + return tx.Find(&models).Error + }) + if err != nil { return nil, err } + records := []*domain.Approval{} for _, m := range models { approval, err := m.ToDomain() if err != nil { @@ -96,11 +97,11 @@ func (r *ApprovalRepository) BulkInsert(ctx context.Context, approvals []*domain if err := m.FromDomain(a); err != nil { return err } - + m.NamespaceID = namespaceFromContext(ctx) models = append(models, m) } - return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + return r.store.Tx(ctx, func(tx *gorm.DB) error { if err := tx.Create(models).Error; err != nil { return err } @@ -123,10 +124,12 @@ func (r *ApprovalRepository) AddApprover(ctx context.Context, approver *domain.A if err := m.FromDomain(approver); err != nil { return fmt.Errorf("parsing approver: %w", err) } - - result := r.db.Create(m) - if result.Error != nil { - return fmt.Errorf("inserting new approver: %w", result.Error) + m.NamespaceID = namespaceFromContext(ctx) + err := r.store.Tx(ctx, func(tx *gorm.DB) error { + return tx.Create(m).Error + }) + if err != nil { + return fmt.Errorf("inserting new approver: %w", err) } newApprover := m.ToDomain() @@ -135,11 +138,13 @@ func (r *ApprovalRepository) AddApprover(ctx context.Context, approver *domain.A } func (r *ApprovalRepository) DeleteApprover(ctx context.Context, approvalID, email string) error { - result := r.db. - WithContext(ctx). - Where("approval_id = ?", approvalID). - Where("email = ?", email). - Delete(&model.Approver{}) + var result *gorm.DB + _ = r.store.Tx(ctx, func(tx *gorm.DB) error { + result = tx.Where("approval_id = ?", approvalID). + Where("email = ?", email). + Delete(&model.Approver{}) + return nil + }) if result.Error != nil { return result.Error } diff --git a/internal/store/postgres/approval_repository_test.go b/internal/store/postgres/approval_repository_test.go index 6f940044e..a1bb8051e 100644 --- a/internal/store/postgres/approval_repository_test.go +++ b/internal/store/postgres/approval_repository_test.go @@ -42,7 +42,7 @@ func (s *ApprovalRepositoryTestSuite) SetupSuite() { s.T().Fatal(err) } - s.repository = postgres.NewApprovalRepository(s.store.DB()) + s.repository = postgres.NewApprovalRepository(s.store) ctx := context.Background() @@ -50,7 +50,7 @@ func (s *ApprovalRepositoryTestSuite) SetupSuite() { ID: "policy_test", Version: 1, } - policyRepository := postgres.NewPolicyRepository(s.store.DB()) + policyRepository := postgres.NewPolicyRepository(s.store) err = policyRepository.Create(ctx, s.dummyPolicy) s.Require().NoError(err) @@ -69,7 +69,7 @@ func (s *ApprovalRepositoryTestSuite) SetupSuite() { }, }, } - providerRepository := postgres.NewProviderRepository(s.store.DB()) + providerRepository := postgres.NewProviderRepository(s.store) err = providerRepository.Create(ctx, s.dummyProvider) s.Require().NoError(err) @@ -80,7 +80,7 @@ func (s *ApprovalRepositoryTestSuite) SetupSuite() { URN: "resource_urn_test", Name: "resource_name_test", } - resourceRepository := postgres.NewResourceRepository(s.store.DB()) + resourceRepository := postgres.NewResourceRepository(s.store) err = resourceRepository.BulkUpsert(ctx, []*domain.Resource{s.dummyResource}) s.Require().NoError(err) @@ -95,7 +95,7 @@ func (s *ApprovalRepositoryTestSuite) SetupSuite() { CreatedBy: "user@example.com", } - s.appealRepository = postgres.NewAppealRepository(s.store.DB()) + s.appealRepository = postgres.NewAppealRepository(s.store) err = s.appealRepository.BulkUpsert(ctx, []*domain.Appeal{s.dummyAppeal}) s.Require().NoError(err) } diff --git a/internal/store/postgres/grant_repository.go b/internal/store/postgres/grant_repository.go index 7603a2647..58478179e 100644 --- a/internal/store/postgres/grant_repository.go +++ b/internal/store/postgres/grant_repository.go @@ -21,68 +21,69 @@ var ( ) type GrantRepository struct { - db *gorm.DB + store *Store } -func NewGrantRepository(db *gorm.DB) *GrantRepository { +func NewGrantRepository(db *Store) *GrantRepository { return &GrantRepository{db} } func (r *GrantRepository) List(ctx context.Context, filter domain.ListGrantsFilter) ([]domain.Grant, error) { - db := r.db.WithContext(ctx) - if filter.AccountIDs != nil { - db = db.Where(`"grants"."account_id" IN ?`, filter.AccountIDs) - } - if filter.AccountTypes != nil { - db = db.Where(`"grants"."account_type" IN ?`, filter.AccountTypes) - } - if filter.ResourceIDs != nil { - db = db.Where(`"grants"."resource_id" IN ?`, filter.ResourceIDs) - } - if filter.Statuses != nil { - db = db.Where(`"grants"."status" IN ?`, filter.Statuses) - } - if filter.Roles != nil { - db = db.Where(`"grants"."role" IN ?`, filter.Roles) - } - if filter.Permissions != nil { - db = db.Where(`"grants"."permissions" @> ?`, pq.StringArray(filter.Permissions)) - } - if filter.Owner != "" { - db = db.Where(`"grants"."owner" = ?`, filter.Owner) - } else if filter.CreatedBy != "" { - db = db.Where(`"grants"."owner" = ?`, filter.CreatedBy) - } - if filter.IsPermanent != nil { - db = db.Where(`"grants"."is_permanent" = ?`, *filter.IsPermanent) - } - if filter.OrderBy != nil { - db = addOrderByClause(db, filter.OrderBy, addOrderByClauseOptions{ - statusColumnName: `"grants"."status"`, - statusesOrder: GrantStatusDefaultSort, - }) - } - if !filter.ExpirationDateLessThan.IsZero() { - db = db.Where(`"grants"."expiration_date" < ?`, filter.ExpirationDateLessThan) - } - if !filter.ExpirationDateGreaterThan.IsZero() { - db = db.Where(`"grants"."expiration_date" > ?`, filter.ExpirationDateGreaterThan) - } - if filter.ProviderTypes != nil { - db = db.Where(`"Resource"."provider_type" IN ?`, filter.ProviderTypes) - } - if filter.ProviderURNs != nil { - db = db.Where(`"Resource"."provider_urn" IN ?`, filter.ProviderURNs) - } - if filter.ResourceTypes != nil { - db = db.Where(`"Resource"."type" IN ?`, filter.ResourceTypes) - } - if filter.ResourceURNs != nil { - db = db.Where(`"Resource"."urn" IN ?`, filter.ResourceURNs) - } - var models []model.Grant - if err := db.Joins("Resource").Joins("Appeal").Find(&models).Error; err != nil { + err := r.store.Tx(ctx, func(tx *gorm.DB) error { + if filter.AccountIDs != nil { + tx = tx.Where(`"grants"."account_id" IN ?`, filter.AccountIDs) + } + if filter.AccountTypes != nil { + tx = tx.Where(`"grants"."account_type" IN ?`, filter.AccountTypes) + } + if filter.ResourceIDs != nil { + tx = tx.Where(`"grants"."resource_id" IN ?`, filter.ResourceIDs) + } + if filter.Statuses != nil { + tx = tx.Where(`"grants"."status" IN ?`, filter.Statuses) + } + if filter.Roles != nil { + tx = tx.Where(`"grants"."role" IN ?`, filter.Roles) + } + if filter.Permissions != nil { + tx = tx.Where(`"grants"."permissions" @> ?`, pq.StringArray(filter.Permissions)) + } + if filter.Owner != "" { + tx = tx.Where(`"grants"."owner" = ?`, filter.Owner) + } else if filter.CreatedBy != "" { + tx = tx.Where(`"grants"."owner" = ?`, filter.CreatedBy) + } + if filter.IsPermanent != nil { + tx = tx.Where(`"grants"."is_permanent" = ?`, *filter.IsPermanent) + } + if filter.OrderBy != nil { + tx = addOrderByClause(tx, filter.OrderBy, addOrderByClauseOptions{ + statusColumnName: `"grants"."status"`, + statusesOrder: GrantStatusDefaultSort, + }) + } + if !filter.ExpirationDateLessThan.IsZero() { + tx = tx.Where(`"grants"."expiration_date" < ?`, filter.ExpirationDateLessThan) + } + if !filter.ExpirationDateGreaterThan.IsZero() { + tx = tx.Where(`"grants"."expiration_date" > ?`, filter.ExpirationDateGreaterThan) + } + if filter.ProviderTypes != nil { + tx = tx.Where(`"Resource"."provider_type" IN ?`, filter.ProviderTypes) + } + if filter.ProviderURNs != nil { + tx = tx.Where(`"Resource"."provider_urn" IN ?`, filter.ProviderURNs) + } + if filter.ResourceTypes != nil { + tx = tx.Where(`"Resource"."type" IN ?`, filter.ResourceTypes) + } + if filter.ResourceURNs != nil { + tx = tx.Where(`"Resource"."urn" IN ?`, filter.ResourceURNs) + } + return tx.Joins("Resource").Joins("Appeal").Find(&models).Error + }) + if err != nil { return nil, err } @@ -100,7 +101,9 @@ func (r *GrantRepository) List(ctx context.Context, filter domain.ListGrantsFilt func (r *GrantRepository) GetByID(ctx context.Context, id string) (*domain.Grant, error) { m := new(model.Grant) - if err := r.db.WithContext(ctx).Joins("Resource").Joins("Appeal").First(&m, `"grants"."id" = ?`, id).Error; err != nil { + if err := r.store.Tx(ctx, func(tx *gorm.DB) error { + return tx.Joins("Resource").Joins("Appeal").First(&m, `"grants"."id" = ?`, id).Error + }); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, grant.ErrGrantNotFound } @@ -123,8 +126,9 @@ func (r *GrantRepository) Update(ctx context.Context, a *domain.Grant) error { if err := m.FromDomain(*a); err != nil { return fmt.Errorf("parsing grant payload: %w", err) } + m.NamespaceID = namespaceFromContext(ctx) - return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + return r.store.Tx(ctx, func(tx *gorm.DB) error { if err := tx.Model(m).Updates(*m).Error; err != nil { return err } @@ -145,12 +149,13 @@ func (r *GrantRepository) BulkInsert(ctx context.Context, grants []*domain.Grant if err := m.FromDomain(*g); err != nil { return fmt.Errorf("serializing grant: %w", err) } + m.NamespaceID = namespaceFromContext(ctx) models = append(models, m) } if len(models) > 0 { - return r.db.Transaction(func(tx *gorm.DB) error { - if err := r.db.Create(models).Error; err != nil { + return r.store.Tx(ctx, func(tx *gorm.DB) error { + if err := tx.Create(models).Error; err != nil { return err } @@ -176,10 +181,12 @@ func (r *GrantRepository) BulkUpsert(ctx context.Context, grants []*domain.Grant if err := m.FromDomain(*g); err != nil { return fmt.Errorf("serializing grant: %w", err) } + m.NamespaceID = namespaceFromContext(ctx) + m.Resource.NamespaceID = m.NamespaceID models = append(models, m) } - return r.db.Transaction(func(tx *gorm.DB) error { + return r.store.Tx(ctx, func(tx *gorm.DB) error { // upsert resources separately to avoid resource upsertion duplicate issue if err := upsertResources(tx, models); err != nil { return fmt.Errorf("upserting resources: %w", err) diff --git a/internal/store/postgres/grant_repository_test.go b/internal/store/postgres/grant_repository_test.go index 79276fed9..d64008489 100644 --- a/internal/store/postgres/grant_repository_test.go +++ b/internal/store/postgres/grant_repository_test.go @@ -45,7 +45,7 @@ func (s *GrantRepositoryTestSuite) SetupSuite() { s.T().Fatal(err) } - s.repository = postgres.NewGrantRepository(s.store.DB()) + s.repository = postgres.NewGrantRepository(s.store) ctx := context.Background() @@ -53,7 +53,7 @@ func (s *GrantRepositoryTestSuite) SetupSuite() { ID: "policy_test", Version: 1, } - policyRepository := postgres.NewPolicyRepository(s.store.DB()) + policyRepository := postgres.NewPolicyRepository(s.store) err = policyRepository.Create(ctx, s.dummyPolicy) s.Require().NoError(err) @@ -72,7 +72,7 @@ func (s *GrantRepositoryTestSuite) SetupSuite() { }, }, } - providerRepository := postgres.NewProviderRepository(s.store.DB()) + providerRepository := postgres.NewProviderRepository(s.store) err = providerRepository.Create(ctx, s.dummyProvider) s.Require().NoError(err) @@ -83,7 +83,7 @@ func (s *GrantRepositoryTestSuite) SetupSuite() { URN: "resource_urn_test", Name: "resource_name_test", } - resourceRepository := postgres.NewResourceRepository(s.store.DB()) + resourceRepository := postgres.NewResourceRepository(s.store) err = resourceRepository.BulkUpsert(ctx, []*domain.Resource{s.dummyResource}) s.Require().NoError(err) @@ -97,7 +97,7 @@ func (s *GrantRepositoryTestSuite) SetupSuite() { Permissions: []string{"permission_test"}, CreatedBy: "user@example.com", } - appealRepository := postgres.NewAppealRepository(s.store.DB()) + appealRepository := postgres.NewAppealRepository(s.store) err = appealRepository.BulkUpsert(ctx, []*domain.Appeal{s.dummyAppeal}) s.Require().NoError(err) } diff --git a/internal/store/postgres/migrations/000016_create_namespace_table_and_add_namespace_in_tables.down.sql b/internal/store/postgres/migrations/000016_create_namespace_table_and_add_namespace_in_tables.down.sql index b3de4e7e6..55403b43e 100644 --- a/internal/store/postgres/migrations/000016_create_namespace_table_and_add_namespace_in_tables.down.sql +++ b/internal/store/postgres/migrations/000016_create_namespace_table_and_add_namespace_in_tables.down.sql @@ -2,15 +2,7 @@ BEGIN; -- drop all index we created ALTER TABLE resources DROP CONSTRAINT fk_resources_provider_type_urn; -ALTER TABLE appeals DROP CONSTRAINT fk_appeals_resource; ALTER TABLE appeals DROP CONSTRAINT fk_appeals_policy_id_version; -ALTER TABLE approvals DROP CONSTRAINT fk_approvals_appeal; -ALTER TABLE approvers DROP CONSTRAINT fk_approvals_approvers; -ALTER TABLE grants DROP CONSTRAINT fk_grants_resource_id; -ALTER TABLE grants DROP CONSTRAINT fk_grants_appeal_id; -ALTER TABLE resources DROP CONSTRAINT fk_resources_parent_id; -ALTER TABLE activities DROP CONSTRAINT fk_activities_provider_id; -ALTER TABLE activities DROP CONSTRAINT fk_activities_resource_id DROP INDEX IF EXISTS activities_provider_activity_provider_idx; DROP INDEX IF EXISTS providers_type_urn; @@ -33,8 +25,11 @@ ALTER TABLE appeals DROP COLUMN IF EXISTS namespace_id; DROP INDEX IF EXISTS idx_approvals_namespace_id; ALTER TABLE approvals DROP COLUMN IF EXISTS namespace_id; -DROP INDEX IF EXISTS idx_audit_logs_namespace_id; -ALTER TABLE audit_logs DROP COLUMN IF EXISTS namespace_id; +DROP INDEX IF EXISTS idx_approvers_namespace_id; +ALTER TABLE approvers DROP COLUMN IF EXISTS namespace_id; + +-- DROP INDEX IF EXISTS idx_audit_logs_namespace_id; +-- ALTER TABLE audit_logs DROP COLUMN IF EXISTS namespace_id; DROP INDEX IF EXISTS idx_grants_namespace_id; ALTER TABLE grants DROP COLUMN IF EXISTS namespace_id; diff --git a/internal/store/postgres/migrations/000016_create_namespace_table_and_add_namespace_in_tables.up.sql b/internal/store/postgres/migrations/000016_create_namespace_table_and_add_namespace_in_tables.up.sql index 659c1613a..20b1ad817 100644 --- a/internal/store/postgres/migrations/000016_create_namespace_table_and_add_namespace_in_tables.up.sql +++ b/internal/store/postgres/migrations/000016_create_namespace_table_and_add_namespace_in_tables.up.sql @@ -1,9 +1,10 @@ BEGIN; +-- add additional columns CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE TABLE IF NOT EXISTS namespaces ( id uuid DEFAULT gen_random_uuid() PRIMARY KEY, - name text UNIQUE NOT NULL, + name text, state text, metadata jsonb, created_at timestamp DEFAULT NOW(), @@ -20,8 +21,12 @@ CREATE INDEX IF NOT EXISTS idx_appeals_namespace_id ON appeals(namespace_id); ALTER TABLE approvals ADD COLUMN IF NOT EXISTS namespace_id uuid NOT NULL DEFAULT uuid_nil(); CREATE INDEX IF NOT EXISTS idx_approvals_namespace_id ON approvals(namespace_id); -ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS namespace_id uuid NOT NULL DEFAULT uuid_nil(); -CREATE INDEX IF NOT EXISTS idx_audit_logs_namespace_id ON audit_logs(namespace_id); +ALTER TABLE approvers ADD COLUMN IF NOT EXISTS namespace_id uuid NOT NULL DEFAULT uuid_nil(); +CREATE INDEX IF NOT EXISTS idx_approvers_namespace_id ON approvers(namespace_id); + +-- not doing it for audit_logs as the table is not owned by us +-- ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS namespace_id uuid NOT NULL DEFAULT uuid_nil(); +-- CREATE INDEX IF NOT EXISTS idx_audit_logs_namespace_id ON audit_logs(namespace_id); ALTER TABLE grants ADD COLUMN IF NOT EXISTS namespace_id uuid NOT NULL DEFAULT uuid_nil(); CREATE INDEX IF NOT EXISTS idx_grants_namespace_id ON grants(namespace_id); @@ -37,47 +42,20 @@ CREATE INDEX IF NOT EXISTS idx_resources_namespace_id ON resources(namespace_id) -- drop all unique index/foreign constraints in use ALTER TABLE resources DROP CONSTRAINT fk_resources_provider; -ALTER TABLE appeals DROP CONSTRAINT fk_appeals_resource; -ALTER TABLE appeals DROP CONSTRAINT fk_appeals_policy; -ALTER TABLE approvals DROP CONSTRAINT fk_approvals_appeal; ALTER TABLE approvals DROP CONSTRAINT fk_appeals_approvals; -ALTER TABLE approvers DROP CONSTRAINT fk_approvals_approvers; -ALTER TABLE grants DROP CONSTRAINT fk_grants_resource; -ALTER TABLE grants DROP CONSTRAINT fk_grants_appeal; -ALTER TABLE resources DROP CONSTRAINT fk_resources_parent; -ALTER TABLE activities DROP CONSTRAINT fk_activities_provider; -ALTER TABLE activities DROP CONSTRAINT fk_activities_resource; - -DROP INDEX IF EXISTS provider_activity_index + +DROP INDEX IF EXISTS provider_activity_index; DROP INDEX IF EXISTS provider_index; DROP INDEX IF EXISTS resource_index; -- include namespace in unique index/foreign constraints -ALTER TABLE resources - ADD CONSTRAINT fk_resources_provider_type_urn FOREIGN KEY (namespace_id,provider_type,provider_urn) - REFERENCES providers(namespace_id,type,urn); -ALTER TABLE appeals - ADD CONSTRAINT fk_appeals_resource FOREIGN KEY (namespace_id,resource_id) REFERENCES resources(namespace_id,id); -ALTER TABLE appeals - ADD CONSTRAINT fk_appeals_policy_id_version FOREIGN KEY (namespace_id,policy_id,policy_version) REFERENCES policies(namespace_id,id,version); -ALTER TABLE approvals - ADD CONSTRAINT fk_approvals_appeal FOREIGN KEY (namespace_id,appeal_id) REFERENCES appeals(namespace_id,id); -ALTER TABLE approvers - ADD CONSTRAINT fk_approvals_approvers FOREIGN KEY (namespace_id,approval_id) REFERENCES approvals(namespace_id,id); -ALTER TABLE grants - ADD CONSTRAINT fk_grants_resource_id FOREIGN KEY (namespace_id,resource_id) REFERENCES resources(namespace_id,id); -ALTER TABLE grants - ADD CONSTRAINT fk_grants_appeal_id FOREIGN KEY (namespace_id,appeal_id) REFERENCES appeals(namespace_id,id); -ALTER TABLE resources - ADD CONSTRAINT fk_resources_parent_id FOREIGN KEY (namespace_id,parent_id) REFERENCES resources(namespace_id,id); -ALTER TABLE activities - ADD CONSTRAINT fk_activities_provider_id FOREIGN KEY (namespace_id,provider_id) REFERENCES providers(namespace_id,id); -ALTER TABLE activities - ADD CONSTRAINT fk_activities_resource_id FOREIGN KEY (namespace_id,resource_id) REFERENCES resources(namespace_id,id); CREATE UNIQUE INDEX activities_provider_activity_provider_idx ON activities(namespace_id, provider_activity_id, provider_id); CREATE UNIQUE INDEX providers_type_urn ON providers(namespace_id,type,urn); CREATE UNIQUE INDEX resources_provider_type_provider_urn_type_urn ON resources(namespace_id,provider_type,provider_urn,type,urn); +ALTER TABLE resources + ADD CONSTRAINT fk_resources_provider_type_urn FOREIGN KEY (namespace_id,provider_type,provider_urn) + REFERENCES providers(namespace_id,type,urn); COMMIT; \ No newline at end of file diff --git a/internal/store/postgres/migrations/000017_enable_row_level_security_all_tables.down.sql b/internal/store/postgres/migrations/000017_enable_row_level_security_all_tables.down.sql index f6b6b1acb..1ee542b77 100644 --- a/internal/store/postgres/migrations/000017_enable_row_level_security_all_tables.down.sql +++ b/internal/store/postgres/migrations/000017_enable_row_level_security_all_tables.down.sql @@ -3,7 +3,8 @@ BEGIN; DROP POLICY IF EXISTS activities_isolation_policy ON activities; DROP POLICY IF EXISTS appeals_isolation_policy ON appeals; DROP POLICY IF EXISTS approvals_isolation_policy ON approvals; -DROP POLICY IF EXISTS audit_logs_isolation_policy ON audit_logs; +DROP POLICY IF EXISTS approvers_isolation_policy ON approvers; +-- DROP POLICY IF EXISTS audit_logs_isolation_policy ON audit_logs; DROP POLICY IF EXISTS grants_isolation_policy ON grants; DROP POLICY IF EXISTS policies_isolation_policy ON policies; DROP POLICY IF EXISTS providers_isolation_policy ON providers; @@ -12,7 +13,8 @@ DROP POLICY IF EXISTS resources_isolation_policy ON resources; ALTER TABLE activities DISABLE ROW LEVEL SECURITY; ALTER TABLE appeals DISABLE ROW LEVEL SECURITY; ALTER TABLE approvals DISABLE ROW LEVEL SECURITY; -ALTER TABLE audit_logs DISABLE ROW LEVEL SECURITY; +ALTER TABLE approvers DISABLE ROW LEVEL SECURITY; +-- ALTER TABLE audit_logs DISABLE ROW LEVEL SECURITY; ALTER TABLE grants DISABLE ROW LEVEL SECURITY; ALTER TABLE policies DISABLE ROW LEVEL SECURITY; ALTER TABLE providers DISABLE ROW LEVEL SECURITY; diff --git a/internal/store/postgres/migrations/000017_enable_row_level_security_all_tables.up.sql b/internal/store/postgres/migrations/000017_enable_row_level_security_all_tables.up.sql index 2bcac2f05..2e40b8673 100644 --- a/internal/store/postgres/migrations/000017_enable_row_level_security_all_tables.up.sql +++ b/internal/store/postgres/migrations/000017_enable_row_level_security_all_tables.up.sql @@ -3,7 +3,8 @@ BEGIN; ALTER TABLE activities ENABLE ROW LEVEL SECURITY; ALTER TABLE appeals ENABLE ROW LEVEL SECURITY; ALTER TABLE approvals ENABLE ROW LEVEL SECURITY; -ALTER TABLE audit_logs ENABLE ROW LEVEL SECURITY; +ALTER TABLE approvers ENABLE ROW LEVEL SECURITY; +-- ALTER TABLE audit_logs ENABLE ROW LEVEL SECURITY; ALTER TABLE grants ENABLE ROW LEVEL SECURITY; ALTER TABLE policies ENABLE ROW LEVEL SECURITY; ALTER TABLE providers ENABLE ROW LEVEL SECURITY; @@ -19,8 +20,11 @@ CREATE POLICY appeals_isolation_policy on appeals USING (namespace_id = current_ DROP POLICY IF EXISTS approvals_isolation_policy ON approvals; CREATE POLICY approvals_isolation_policy on approvals USING (namespace_id = current_setting('app.current_tenant')::UUID); -DROP POLICY IF EXISTS audit_logs_isolation_policy ON audit_logs; -CREATE POLICY audit_logs_isolation_policy on audit_logs USING (namespace_id = current_setting('app.current_tenant')::UUID); +DROP POLICY IF EXISTS approvers_isolation_policy ON approvals; +CREATE POLICY approvers_isolation_policy on approvers USING (namespace_id = current_setting('app.current_tenant')::UUID); + +-- DROP POLICY IF EXISTS audit_logs_isolation_policy ON audit_logs; +-- CREATE POLICY audit_logs_isolation_policy on audit_logs USING (namespace_id = current_setting('app.current_tenant')::UUID); DROP POLICY IF EXISTS grants_isolation_policy ON grants; CREATE POLICY grants_isolation_policy on grants USING (namespace_id = current_setting('app.current_tenant')::UUID); diff --git a/internal/store/postgres/model/activity.go b/internal/store/postgres/model/activity.go index 580aaf709..51c40aa98 100644 --- a/internal/store/postgres/model/activity.go +++ b/internal/store/postgres/model/activity.go @@ -13,6 +13,7 @@ import ( type Activity struct { ID uuid.UUID `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"` + NamespaceID uuid.UUID `gorm:"type:uuid"` ProviderID uuid.UUID ResourceID uuid.UUID ProviderActivityID string @@ -83,7 +84,6 @@ func (m *Activity) FromDomain(a *domain.Activity) error { return fmt.Errorf("failed to convert resource: %w", err) } } - return nil } diff --git a/internal/store/postgres/model/appeal.go b/internal/store/postgres/model/appeal.go index 49c1d6c78..dc26def56 100644 --- a/internal/store/postgres/model/appeal.go +++ b/internal/store/postgres/model/appeal.go @@ -15,6 +15,7 @@ import ( // Appeal database model type Appeal struct { ID uuid.UUID `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"` + NamespaceID uuid.UUID `gorm:"type:uuid"` ResourceID string PolicyID string PolicyVersion uint diff --git a/internal/store/postgres/model/approval.go b/internal/store/postgres/model/approval.go index 53b9d39f1..d359e3b69 100644 --- a/internal/store/postgres/model/approval.go +++ b/internal/store/postgres/model/approval.go @@ -12,6 +12,7 @@ import ( // Approval database model type Approval struct { ID uuid.UUID `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"` + NamespaceID uuid.UUID `gorm:"type:uuid"` Name string `gorm:"index"` Index int AppealID string diff --git a/internal/store/postgres/model/approver.go b/internal/store/postgres/model/approver.go index 8cd623f48..17184ca47 100644 --- a/internal/store/postgres/model/approver.go +++ b/internal/store/postgres/model/approver.go @@ -11,10 +11,11 @@ import ( // Approver database model type Approver struct { - ID uuid.UUID `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"` - ApprovalID string - AppealID string `gorm:"index"` - Email string `gorm:"index"` + ID uuid.UUID `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"` + NamespaceID uuid.UUID `gorm:"type:uuid"` + ApprovalID string + AppealID string `gorm:"index"` + Email string `gorm:"index"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` diff --git a/internal/store/postgres/model/grant.go b/internal/store/postgres/model/grant.go index b323910ae..7eac78b51 100644 --- a/internal/store/postgres/model/grant.go +++ b/internal/store/postgres/model/grant.go @@ -13,6 +13,7 @@ import ( type Grant struct { ID uuid.UUID `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"` + NamespaceID uuid.UUID `gorm:"type:uuid"` Status string StatusInProvider string AccountID string diff --git a/internal/store/postgres/model/namespace.go b/internal/store/postgres/model/namespace.go new file mode 100644 index 000000000..25ae28507 --- /dev/null +++ b/internal/store/postgres/model/namespace.go @@ -0,0 +1,65 @@ +package model + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/raystack/guardian/domain" + "gorm.io/datatypes" +) + +type Namespace struct { + ID uuid.UUID `gorm:"type:uuid;primaryKey"` + Name string + State string + Metadata datatypes.JSON + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` +} + +func (Namespace) TableName() string { + return "namespaces" +} + +func (m *Namespace) FromDomain(a *domain.Namespace) error { + if a.ID != "" { + id, err := uuid.Parse(a.ID) + if err != nil { + return fmt.Errorf("failed to parse id: %w", err) + } + m.ID = id + } + + m.Name = a.Name + m.State = a.State + if a.Metadata != nil { + metadata, err := json.Marshal(a.Metadata) + if err != nil { + return fmt.Errorf("failed to marshal provider namespace metadata: %w", err) + } + m.Metadata = metadata + } + m.CreatedAt = a.CreatedAt + m.UpdatedAt = a.UpdatedAt + return nil +} + +func (m *Namespace) ToDomain(a *domain.Namespace) error { + if a == nil { + return fmt.Errorf("namespace target can't be nil") + } + a.ID = m.ID.String() + a.Name = m.Name + a.State = m.State + a.UpdatedAt = m.UpdatedAt + a.CreatedAt = m.CreatedAt + + if m.Metadata != nil { + if err := json.Unmarshal(m.Metadata, &a.Metadata); err != nil { + return fmt.Errorf("failed to unmarshal provider activity metadata: %w", err) + } + } + return nil +} diff --git a/internal/store/postgres/model/policy.go b/internal/store/postgres/model/policy.go index 841653a7c..21a048f29 100644 --- a/internal/store/postgres/model/policy.go +++ b/internal/store/postgres/model/policy.go @@ -4,6 +4,8 @@ import ( "encoding/json" "time" + "github.com/google/uuid" + "github.com/raystack/guardian/domain" "gorm.io/datatypes" "gorm.io/gorm" @@ -11,8 +13,9 @@ import ( // Policy is the database model for policy type Policy struct { - ID string `gorm:"primaryKey"` - Version uint `gorm:"primaryKey"` + ID string `gorm:"primaryKey"` + NamespaceID uuid.UUID `gorm:"type:uuid"` + Version uint `gorm:"primaryKey"` Description string Steps datatypes.JSON AppealConfig datatypes.JSON diff --git a/internal/store/postgres/model/provider.go b/internal/store/postgres/model/provider.go index f548b0a98..d8bbe6424 100644 --- a/internal/store/postgres/model/provider.go +++ b/internal/store/postgres/model/provider.go @@ -13,13 +13,14 @@ import ( // Provider is the database model for provider type Provider struct { - ID uuid.UUID `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"` - Type string `gorm:"uniqueIndex:provider_index"` - URN string `gorm:"uniqueIndex:provider_index"` - Config datatypes.JSON - CreatedAt time.Time `gorm:"autoCreateTime"` - UpdatedAt time.Time `gorm:"autoUpdateTime"` - DeletedAt gorm.DeletedAt `gorm:"index"` + ID uuid.UUID `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"` + NamespaceID uuid.UUID `gorm:"type:uuid"` + Type string `gorm:"uniqueIndex:providers_type_urn"` + URN string `gorm:"uniqueIndex:providers_type_urn"` + Config datatypes.JSON + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index"` } // TableName overrides the table name @@ -45,7 +46,7 @@ func (m *Provider) FromDomain(p *domain.Provider) error { m.ID = id m.Type = p.Type m.URN = p.URN - m.Config = datatypes.JSON(config) + m.Config = config m.CreatedAt = p.CreatedAt m.UpdatedAt = p.UpdatedAt diff --git a/internal/store/postgres/model/resource.go b/internal/store/postgres/model/resource.go index 49200feef..0775df117 100644 --- a/internal/store/postgres/model/resource.go +++ b/internal/store/postgres/model/resource.go @@ -15,11 +15,12 @@ import ( // Resource is the database model for resource type Resource struct { ID uuid.UUID `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"` + NamespaceID uuid.UUID `gorm:"type:uuid"` ParentID *string `gorm:"type:uuid"` - ProviderType string `gorm:"uniqueIndex:resource_index"` - ProviderURN string `gorm:"uniqueIndex:resource_index"` - Type string `gorm:"uniqueIndex:resource_index"` - URN string `gorm:"uniqueIndex:resource_index"` + ProviderType string `gorm:"uniqueIndex:resources_provider_type_provider_urn_type_urn"` + ProviderURN string `gorm:"uniqueIndex:resources_provider_type_provider_urn_type_urn"` + Type string `gorm:"uniqueIndex:resources_provider_type_provider_urn_type_urn"` + URN string `gorm:"uniqueIndex:resources_provider_type_provider_urn_type_urn"` Name string Details datatypes.JSON Labels datatypes.JSON @@ -41,6 +42,7 @@ func (Resource) TableName() string { func (r *Resource) BeforeCreate(tx *gorm.DB) error { tx.Statement.AddClause(clause.OnConflict{ Columns: []clause.Column{ + {Name: "namespace_id"}, {Name: "provider_type"}, {Name: "provider_urn"}, {Name: "type"}, diff --git a/internal/store/postgres/namespace_repository.go b/internal/store/postgres/namespace_repository.go new file mode 100644 index 000000000..8f575e131 --- /dev/null +++ b/internal/store/postgres/namespace_repository.go @@ -0,0 +1,79 @@ +package postgres + +import ( + "context" + "errors" + "fmt" + + "github.com/raystack/guardian/domain" + "github.com/raystack/guardian/internal/store/postgres/model" + "gorm.io/gorm" +) + +type NamespaceRepository struct { + store *Store +} + +func NewNamespaceRepository(store *Store) *NamespaceRepository { + return &NamespaceRepository{ + store: store, + } +} + +func (r *NamespaceRepository) List(ctx context.Context) ([]*domain.Namespace, error) { + var namespaces []*model.Namespace + + if err := r.store.Tx(ctx, func(tx *gorm.DB) error { + return tx.Find(&namespaces).Error + }); err != nil { + return nil, err + } + + var results []*domain.Namespace + for _, activity := range namespaces { + a := &domain.Namespace{} + if err := activity.ToDomain(a); err != nil { + return nil, fmt.Errorf("failed to convert model %q to domain: %w", activity.ID, err) + } + results = append(results, a) + } + return results, nil +} + +func (r *NamespaceRepository) GetOne(ctx context.Context, id string) (*domain.Namespace, error) { + var m model.Namespace + if err := r.store.Tx(ctx, func(tx *gorm.DB) error { + return tx.Where("id = ?", id).First(&m).Error + }); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("namespace not found") + } + return nil, err + } + a := &domain.Namespace{} + if err := m.ToDomain(a); err != nil { + return nil, err + } + return a, nil +} + +func (r *NamespaceRepository) BulkUpsert(ctx context.Context, namespaces []*domain.Namespace) error { + if len(namespaces) == 0 { + return nil + } + + namespaceModels := make([]*model.Namespace, len(namespaces)) + for i, a := range namespaces { + namespaceModels[i] = &model.Namespace{} + if err := namespaceModels[i].FromDomain(a); err != nil { + return fmt.Errorf("failed to convert domain to model: %w", err) + } + } + + return r.store.Tx(ctx, func(tx *gorm.DB) error { + if err := tx.Save(namespaceModels).Error; err != nil { + return fmt.Errorf("failed to upsert provider namespaces: %w", err) + } + return nil + }) +} diff --git a/internal/store/postgres/policy_repository.go b/internal/store/postgres/policy_repository.go index 0d16821ed..181b8250b 100644 --- a/internal/store/postgres/policy_repository.go +++ b/internal/store/postgres/policy_repository.go @@ -12,11 +12,11 @@ import ( // PolicyRepository talks to the store to read or insert data type PolicyRepository struct { - db *gorm.DB + store *Store } // NewPolicyRepository returns repository struct -func NewPolicyRepository(db *gorm.DB) *PolicyRepository { +func NewPolicyRepository(db *Store) *PolicyRepository { return &PolicyRepository{db} } @@ -26,8 +26,9 @@ func (r *PolicyRepository) Create(ctx context.Context, p *domain.Policy) error { if err := m.FromDomain(p); err != nil { return fmt.Errorf("serializing policy: %w", err) } + m.NamespaceID = namespaceFromContext(ctx) - return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + return r.store.Tx(ctx, func(tx *gorm.DB) error { if result := tx.Create(m); result.Error != nil { return result.Error } @@ -48,7 +49,9 @@ func (r *PolicyRepository) Find(ctx context.Context) ([]*domain.Policy, error) { policies := []*domain.Policy{} var models []*model.Policy - if err := r.db.WithContext(ctx).Find(&models).Error; err != nil { + if err := r.store.Tx(ctx, func(tx *gorm.DB) error { + return tx.Find(&models).Error + }); err != nil { return nil, err } for _, m := range models { @@ -75,7 +78,9 @@ func (r *PolicyRepository) GetOne(ctx context.Context, id string, version uint) } conds := append([]interface{}{condition}, args...) - if err := r.db.WithContext(ctx).Order("version desc").First(m, conds...).Error; err != nil { + if err := r.store.Tx(ctx, func(tx *gorm.DB) error { + return tx.Order("version desc").First(m, conds...).Error + }); err != nil { if err == gorm.ErrRecordNotFound { return nil, policy.ErrPolicyNotFound } diff --git a/internal/store/postgres/policy_repository_test.go b/internal/store/postgres/policy_repository_test.go index 72cbea8e8..ce3703936 100644 --- a/internal/store/postgres/policy_repository_test.go +++ b/internal/store/postgres/policy_repository_test.go @@ -33,7 +33,7 @@ func (s *PolicyRepositoryTestSuite) SetupSuite() { s.T().Fatal(err) } - s.repository = postgres.NewPolicyRepository(s.store.DB()) + s.repository = postgres.NewPolicyRepository(s.store) } func (s *PolicyRepositoryTestSuite) TearDownSuite() { diff --git a/internal/store/postgres/provider_repository.go b/internal/store/postgres/provider_repository.go index b26efc4e8..295814805 100644 --- a/internal/store/postgres/provider_repository.go +++ b/internal/store/postgres/provider_repository.go @@ -12,11 +12,11 @@ import ( // ProviderRepository talks to the store to read or insert data type ProviderRepository struct { - db *gorm.DB + store *Store } // NewProviderRepository returns repository struct -func NewProviderRepository(db *gorm.DB) *ProviderRepository { +func NewProviderRepository(db *Store) *ProviderRepository { return &ProviderRepository{db} } @@ -26,8 +26,9 @@ func (r *ProviderRepository) Create(ctx context.Context, p *domain.Provider) err if err := m.FromDomain(p); err != nil { return err } + m.NamespaceID = namespaceFromContext(ctx) - return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + return r.store.Tx(ctx, func(tx *gorm.DB) error { if result := tx.Create(m); result.Error != nil { return result.Error } @@ -48,7 +49,9 @@ func (r *ProviderRepository) Find(ctx context.Context) ([]*domain.Provider, erro providers := []*domain.Provider{} var models []*model.Provider - if err := r.db.WithContext(ctx).Find(&models).Error; err != nil { + if err := r.store.Tx(ctx, func(tx *gorm.DB) error { + return tx.Find(&models).Error + }); err != nil { return nil, err } for _, m := range models { @@ -70,7 +73,9 @@ func (r *ProviderRepository) GetByID(ctx context.Context, id string) (*domain.Pr } var m model.Provider - if err := r.db.WithContext(ctx).First(&m, "id = ?", id).Error; err != nil { + if err := r.store.Tx(ctx, func(tx *gorm.DB) error { + return tx.First(&m, "id = ?", id).Error + }); err != nil { if err == gorm.ErrRecordNotFound { return nil, provider.ErrRecordNotFound } @@ -91,9 +96,9 @@ func (r *ProviderRepository) GetTypes(ctx context.Context) ([]domain.ProviderTyp ResourceType string } - r.db.WithContext(ctx). - Raw("select distinct provider_type, type as resource_type from resources").Scan(&results) - + _ = r.store.Tx(ctx, func(tx *gorm.DB) error { + return tx.Raw("select distinct provider_type, type as resource_type from resources").Scan(&results).Error + }) if len(results) == 0 { return nil, errors.New("no provider types found") } @@ -128,8 +133,10 @@ func (r *ProviderRepository) GetOne(ctx context.Context, pType, urn string) (*do } m := &model.Provider{} - db := r.db.WithContext(ctx).Where("type = ?", pType).Where("urn = ?", urn) - if err := db.Take(m).Error; err != nil { + err := r.store.Tx(ctx, func(tx *gorm.DB) error { + return tx.Where("type = ?", pType).Where("urn = ?", urn).Take(m).Error + }) + if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, provider.ErrRecordNotFound } @@ -154,8 +161,9 @@ func (r *ProviderRepository) Update(ctx context.Context, p *domain.Provider) err if err := m.FromDomain(p); err != nil { return err } + m.NamespaceID = namespaceFromContext(ctx) - return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + return r.store.Tx(ctx, func(tx *gorm.DB) error { if err := tx.Model(m).Updates(*m).Error; err != nil { return err } @@ -176,8 +184,11 @@ func (r *ProviderRepository) Delete(ctx context.Context, id string) error { if id == "" { return provider.ErrEmptyIDParam } - - result := r.db.WithContext(ctx).Where("id = ?", id).Delete(&model.Provider{}) + var result *gorm.DB + r.store.Tx(ctx, func(tx *gorm.DB) error { + result = tx.Where("id = ?", id).Delete(&model.Provider{}) + return nil + }) if result.Error != nil { return result.Error } diff --git a/internal/store/postgres/provider_repository_test.go b/internal/store/postgres/provider_repository_test.go index 3d424d0d4..2cd53e581 100644 --- a/internal/store/postgres/provider_repository_test.go +++ b/internal/store/postgres/provider_repository_test.go @@ -36,9 +36,9 @@ func (s *ProviderRepositoryTestSuite) SetupSuite() { s.T().Fatal(err) } - s.repository = postgres.NewProviderRepository(s.store.DB()) - s.resourceRepository = postgres.NewResourceRepository(s.store.DB()) - s.providerRepository = postgres.NewProviderRepository(s.store.DB()) + s.repository = postgres.NewProviderRepository(s.store) + s.resourceRepository = postgres.NewResourceRepository(s.store) + s.providerRepository = postgres.NewProviderRepository(s.store) } func (s *ProviderRepositoryTestSuite) TearDownSuite() { diff --git a/internal/store/postgres/resource_repository.go b/internal/store/postgres/resource_repository.go index 5c971db46..7b8284813 100644 --- a/internal/store/postgres/resource_repository.go +++ b/internal/store/postgres/resource_repository.go @@ -13,11 +13,11 @@ import ( // ResourceRepository talks to the store/database to read/insert data type ResourceRepository struct { - db *gorm.DB + store *Store } // NewResourceRepository returns *Repository -func NewResourceRepository(db *gorm.DB) *ResourceRepository { +func NewResourceRepository(db *Store) *ResourceRepository { return &ResourceRepository{db} } @@ -27,41 +27,42 @@ func (r *ResourceRepository) Find(ctx context.Context, filter domain.ListResourc return nil, err } - db := r.db.WithContext(ctx) - if filter.IDs != nil { - db = db.Where(filter.IDs) - } - if !filter.IsDeleted { - db = db.Where(`"is_deleted" = ?`, filter.IsDeleted) - } - if filter.ResourceType != "" { - db = db.Where(`"type" = ?`, filter.ResourceType) - } - if filter.Name != "" { - db = db.Where(`"name" = ?`, filter.Name) - } - if filter.ProviderType != "" { - db = db.Where(`"provider_type" = ?`, filter.ProviderType) - } - if filter.ProviderURN != "" { - db = db.Where(`"provider_urn" = ?`, filter.ProviderURN) - } - if filter.ResourceURN != "" { - db = db.Where(`"urn" = ?`, filter.ResourceURN) - } - if filter.ResourceURNs != nil { - db = db.Where(`"urn" IN ?`, filter.ResourceURNs) - } - if filter.ResourceTypes != nil { - db = db.Where(`"type" IN ?`, filter.ResourceTypes) - } - for path, v := range filter.Details { - pathArr := "{" + strings.Join(strings.Split(path, "."), ",") + "}" - db = db.Where(`"details" #>> ? = ?`, pathArr, v) - } - var models []*model.Resource - if err := db.Find(&models).Error; err != nil { + err := r.store.Tx(ctx, func(tx *gorm.DB) error { + if filter.IDs != nil { + tx = tx.Where(filter.IDs) + } + if !filter.IsDeleted { + tx = tx.Where(`"is_deleted" = ?`, filter.IsDeleted) + } + if filter.ResourceType != "" { + tx = tx.Where(`"type" = ?`, filter.ResourceType) + } + if filter.Name != "" { + tx = tx.Where(`"name" = ?`, filter.Name) + } + if filter.ProviderType != "" { + tx = tx.Where(`"provider_type" = ?`, filter.ProviderType) + } + if filter.ProviderURN != "" { + tx = tx.Where(`"provider_urn" = ?`, filter.ProviderURN) + } + if filter.ResourceURN != "" { + tx = tx.Where(`"urn" = ?`, filter.ResourceURN) + } + if filter.ResourceURNs != nil { + tx = tx.Where(`"urn" IN ?`, filter.ResourceURNs) + } + if filter.ResourceTypes != nil { + tx = tx.Where(`"type" IN ?`, filter.ResourceTypes) + } + for path, v := range filter.Details { + pathArr := "{" + strings.Join(strings.Split(path, "."), ",") + "}" + tx = tx.Where(`"details" #>> ? = ?`, pathArr, v) + } + return tx.Find(&models).Error + }) + if err != nil { return nil, err } @@ -85,7 +86,9 @@ func (r *ResourceRepository) GetOne(ctx context.Context, id string) (*domain.Res } var m model.Resource - if err := r.db.WithContext(ctx).Where("id = ?", id).Take(&m).Error; err != nil { + if err := r.store.Tx(ctx, func(tx *gorm.DB) error { + return tx.Where("id = ?", id).Take(&m).Error + }); err != nil { if err == gorm.ErrRecordNotFound { return nil, resource.ErrRecordNotFound } @@ -108,14 +111,14 @@ func (r *ResourceRepository) BulkUpsert(ctx context.Context, resources []*domain if err := m.FromDomain(r); err != nil { return err } - + m.NamespaceID = namespaceFromContext(ctx) models = append(models, m) } if len(models) > 0 { - return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + return r.store.Tx(ctx, func(tx *gorm.DB) error { // upsert clause is moved to model.Resource.BeforeCreate() (gorm's hook) to apply the same for associations (model.Resource.Children) - if err := r.db.Create(models).Error; err != nil { + if err := tx.Create(models).Error; err != nil { return err } @@ -144,8 +147,9 @@ func (r *ResourceRepository) Update(ctx context.Context, res *domain.Resource) e if err := m.FromDomain(res); err != nil { return err } + m.NamespaceID = namespaceFromContext(ctx) - return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + return r.store.Tx(ctx, func(tx *gorm.DB) error { if err := tx.Model(m).Where("id = ?", m.ID).Updates(*m).Error; err != nil { return err } @@ -165,8 +169,11 @@ func (r *ResourceRepository) Delete(ctx context.Context, id string) error { if id == "" { return resource.ErrEmptyIDParam } - - result := r.db.WithContext(ctx).Where("id = ?", id).Delete(&model.Resource{}) + var result *gorm.DB + _ = r.store.Tx(ctx, func(tx *gorm.DB) error { + result = tx.Where("id = ?", id).Delete(&model.Resource{}) + return nil + }) if result.Error != nil { return result.Error } @@ -181,8 +188,11 @@ func (r *ResourceRepository) BatchDelete(ctx context.Context, ids []string) erro if ids == nil { return resource.ErrEmptyIDParam } - - result := r.db.WithContext(ctx).Delete(&model.Resource{}, ids) + var result *gorm.DB + _ = r.store.Tx(ctx, func(tx *gorm.DB) error { + result = tx.Delete(&model.Resource{}, ids) + return nil + }) if result.Error != nil { return result.Error } diff --git a/internal/store/postgres/resource_repository_test.go b/internal/store/postgres/resource_repository_test.go index 1c5ef1651..96c3fcd61 100644 --- a/internal/store/postgres/resource_repository_test.go +++ b/internal/store/postgres/resource_repository_test.go @@ -34,13 +34,13 @@ func (s *ResourceRepositoryTestSuite) SetupSuite() { s.T().Fatal(err) } - s.repository = postgres.NewResourceRepository(s.store.DB()) + s.repository = postgres.NewResourceRepository(s.store) s.dummyProvider = &domain.Provider{ Type: "provider_test", URN: "provider_urn_test", } - providerRepository := postgres.NewProviderRepository(s.store.DB()) + providerRepository := postgres.NewProviderRepository(s.store) err = providerRepository.Create(context.Background(), s.dummyProvider) s.Require().NoError(err) } diff --git a/internal/store/postgres/store.go b/internal/store/postgres/store.go index a18e513d6..7627b539f 100644 --- a/internal/store/postgres/store.go +++ b/internal/store/postgres/store.go @@ -1,6 +1,7 @@ package postgres import ( + "context" "embed" "errors" "fmt" @@ -9,6 +10,9 @@ import ( "net/url" "strings" + "github.com/google/uuid" + "github.com/raystack/guardian/pkg/auth" + "github.com/golang-migrate/migrate/v4" _ "github.com/golang-migrate/migrate/v4/database/postgres" "github.com/golang-migrate/migrate/v4/source/iofs" @@ -26,6 +30,15 @@ type Store struct { config *store.Config } +const ( + namespaceRLSSetQuery = "SET app.current_tenant = '%s'" + namespaceRLSResetQuery = "RESET app.current_tenant" +) + +type Connection interface { + Tx(ctx context.Context, fc func(tx *gorm.DB) error) error +} + func NewStore(c *store.Config) (*Store, error) { dsn := fmt.Sprintf( "host=%s user=%s dbname=%s port=%s sslmode=%s password=%s", @@ -52,6 +65,24 @@ func (s *Store) DB() *gorm.DB { return s.db } +func (s *Store) Tx(ctx context.Context, fc func(tx *gorm.DB) error) error { + return s.db.WithContext(ctx).Connection(func(conn *gorm.DB) error { + return conn.Transaction(func(tx *gorm.DB) error { + // set tenant context + if err := tx.Exec(fmt.Sprintf(namespaceRLSSetQuery, namespaceFromContext(ctx))).Error; err != nil { + return err + } + + // execute the requested operation + fcErr := fc(tx) + + // reset tenant context + _ = tx.Exec(namespaceRLSResetQuery).Error + return fcErr + }) + }) +} + func (s *Store) Migrate() error { iofsDriver, err := iofs.New(fs, "migrations") if err != nil { @@ -88,3 +119,7 @@ func toConnectionString(c *store.Config) string { return pgURL.String() } + +func namespaceFromContext(ctx context.Context) uuid.UUID { + return auth.FetchNamespace(ctx) +} diff --git a/pkg/auth/frontier.go b/pkg/auth/frontier.go new file mode 100644 index 000000000..fce934b1d --- /dev/null +++ b/pkg/auth/frontier.go @@ -0,0 +1,53 @@ +package auth + +import ( + "context" + "strings" + + "github.com/google/uuid" + "github.com/lestrrat-go/jwx/v2/jwt" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +type FrontierConfig struct { + Host string `mapstructure:"host"` + NamespaceClaimsKey string `mapstructure:"namespace_claims_key" default:"project_id"` +} + +type namespaceContextKey struct{} + +// FrontierJWTInterceptor extracts the frontier jwt from the request metadata and +// set the context with the extracted jwt claims +func FrontierJWTInterceptor(conf FrontierConfig) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + if md, ok := metadata.FromIncomingContext(ctx); ok { + if v := md.Get("authorization"); len(v) > 0 && len(v[0]) > 0 { + var frontierToken string + if strings.HasPrefix(v[0], "Bearer ") { + frontierToken = strings.TrimPrefix(v[0], "Bearer ") + } + if frontierToken != "" { + // TODO(kushsharma): we should validate the token using frontier public key + insecureToken, err := jwt.ParseInsecure([]byte(frontierToken)) + if err == nil { + if namespace, claimExists := insecureToken.Get(conf.NamespaceClaimsKey); claimExists { + if id, err := uuid.Parse(namespace.(string)); err == nil { + ctx = context.WithValue(ctx, namespaceContextKey{}, id) + } + } + } + } + } + } + + return handler(ctx, req) + } +} + +func FetchNamespace(ctx context.Context) uuid.UUID { + if namespace, ok := ctx.Value(namespaceContextKey{}).(uuid.UUID); ok { + return namespace + } + return uuid.Nil +}