From 9b8d144dfd2f2c05209e1e5c3f15ba59c8899da4 Mon Sep 17 00:00:00 2001 From: Dominic Wong Date: Tue, 1 Feb 2022 12:09:33 +0000 Subject: [PATCH] Delete data part 1 (#360) --- cache/handler/cache.go | 29 +++++++++++++++++++++++++ cache/main.go | 5 ++++- contact/handler/contact.go | 43 ++++++++++++++++++++++++++++++++++++++ contact/main.go | 6 ++++-- db/handler/db.go | 43 ++++++++++++++++++++++++++++++++++++++ db/main.go | 5 ++--- pkg/auth/auth.go | 34 ++++++++++++++++++++++++++++++ pkg/tenant/tenant.go | 17 ++++++++++++++- user/handler/handler.go | 30 ++------------------------ 9 files changed, 177 insertions(+), 35 deletions(-) create mode 100644 pkg/auth/auth.go diff --git a/cache/handler/cache.go b/cache/handler/cache.go index 735124b..2f7f5d8 100644 --- a/cache/handler/cache.go +++ b/cache/handler/cache.go @@ -9,7 +9,10 @@ import ( "github.com/micro/micro/v3/service/errors" log "github.com/micro/micro/v3/service/logger" pb "github.com/micro/services/cache/proto" + pauth "github.com/micro/services/pkg/auth" "github.com/micro/services/pkg/cache" + adminpb "github.com/micro/services/pkg/service/proto" + "github.com/micro/services/pkg/tenant" ) type Cache struct{} @@ -133,3 +136,29 @@ func (c *Cache) ListKeys(ctx context.Context, req *pb.ListKeysRequest, rsp *pb.L return nil } + +func (c *Cache) DeleteData(ctx context.Context, request *adminpb.DeleteDataRequest, response *adminpb.DeleteDataResponse) error { + method := "admin.DeleteData" + _, err := pauth.VerifyMicroAdmin(ctx, method) + if err != nil { + return err + } + + if len(request.TenantId) == 0 { + return errors.BadRequest(method, "Missing tenant ID") + } + + split := strings.Split(request.TenantId, "/") + tctx := tenant.NewContext(split[1], split[0], split[1]) + keys, err := cache.Context(tctx).ListKeys() + if err != nil { + return err + } + for _, k := range keys { + if err := cache.Context(tctx).Delete(k); err != nil { + return err + } + } + log.Infof("Deleted %d keys for %s", len(keys), request.TenantId) + return nil +} diff --git a/cache/main.go b/cache/main.go index bcf280d..bf63224 100644 --- a/cache/main.go +++ b/cache/main.go @@ -3,6 +3,7 @@ package main import ( "github.com/micro/services/cache/handler" pb "github.com/micro/services/cache/proto" + adminpb "github.com/micro/services/pkg/service/proto" "github.com/micro/services/pkg/tracing" "github.com/micro/micro/v3/service" @@ -17,7 +18,9 @@ func main() { ) // Register handler - pb.RegisterCacheHandler(srv.Server(), new(handler.Cache)) + c := new(handler.Cache) + pb.RegisterCacheHandler(srv.Server(), c) + adminpb.RegisterAdminHandler(srv.Server(), c) traceCloser := tracing.SetupOpentracing("cache") defer traceCloser.Close() diff --git a/contact/handler/contact.go b/contact/handler/contact.go index 9827433..2f2c7a5 100644 --- a/contact/handler/contact.go +++ b/contact/handler/contact.go @@ -6,6 +6,10 @@ import ( "github.com/google/uuid" "github.com/micro/micro/v3/service/errors" + "github.com/micro/micro/v3/service/logger" + pauth "github.com/micro/services/pkg/auth" + adminpb "github.com/micro/services/pkg/service/proto" + "github.com/micro/services/pkg/tenant" "github.com/micro/services/contact/domain" pb "github.com/micro/services/contact/proto" @@ -125,3 +129,42 @@ func (c *contact) List(ctx context.Context, req *pb.ListRequest, rsp *pb.ListRes return nil } + +func (c *contact) DeleteData(ctx context.Context, request *adminpb.DeleteDataRequest, response *adminpb.DeleteDataResponse) error { + method := "admin.DeleteData" + _, err := pauth.VerifyMicroAdmin(ctx, method) + if err != nil { + return err + } + + if len(request.TenantId) == 0 { + return errors.BadRequest(method, "Missing tenant ID") + } + + split := strings.Split(request.TenantId, "/") + tctx := tenant.NewContext(split[1], split[0], split[1]) + // load all keys + keys := []string{} + offset := uint(0) + for { + res, err := c.contact.List(tctx, offset, 100) + if err != nil && !strings.Contains(err.Error(), "not found") { + return err + } + for _, r := range res { + keys = append(keys, r.Id) + } + if len(res) < 100 { + break + } + offset += 100 + } + for _, k := range keys { + if err := c.contact.Delete(tctx, k); err != nil { + return err + } + } + + logger.Infof("Deleted %d keys for %s", len(keys), request.TenantId) + return nil +} diff --git a/contact/main.go b/contact/main.go index eb821b2..731e48a 100644 --- a/contact/main.go +++ b/contact/main.go @@ -2,6 +2,7 @@ package main import ( "github.com/micro/micro/v3/service/store" + admin "github.com/micro/services/pkg/service/proto" "github.com/micro/services/contact/domain" "github.com/micro/services/contact/handler" @@ -20,9 +21,10 @@ func main() { contactDomain := domain.NewContactDomain(store.DefaultStore) + h := handler.NewContact(contactDomain) // Register handler - pb.RegisterContactHandler(srv.Server(), handler.NewContact(contactDomain)) - + pb.RegisterContactHandler(srv.Server(), h) + admin.RegisterAdminHandler(srv.Server(), h) // Run service if err := srv.Run(); err != nil { logger.Fatal(err) diff --git a/db/handler/db.go b/db/handler/db.go index 550b0f0..d582286 100644 --- a/db/handler/db.go +++ b/db/handler/db.go @@ -12,7 +12,9 @@ import ( "github.com/micro/micro/v3/service/errors" "github.com/micro/micro/v3/service/logger" db "github.com/micro/services/db/proto" + pauth "github.com/micro/services/pkg/auth" gorm2 "github.com/micro/services/pkg/gorm" + adminpb "github.com/micro/services/pkg/service/proto" "github.com/micro/services/pkg/tenant" "github.com/patrickmn/go-cache" "google.golang.org/protobuf/types/known/structpb" @@ -441,3 +443,44 @@ func (e *Db) ListTables(ctx context.Context, req *db.ListTablesRequest, rsp *db. } return nil } + +func (e *Db) DeleteData(ctx context.Context, request *adminpb.DeleteDataRequest, response *adminpb.DeleteDataResponse) error { + method := "admin.DeleteData" + _, err := pauth.VerifyMicroAdmin(ctx, method) + if err != nil { + return err + } + + if len(request.TenantId) == 0 { + return errors.BadRequest(method, "Missing tenant ID") + } + + split := strings.Split(request.TenantId, "/") + tctx := tenant.NewContext(split[1], split[0], split[1]) + + tenantId := request.TenantId + tenantId = strings.Replace(strings.Replace(tenantId, "/", "_", -1), "-", "_", -1) + + db, err := e.GetDBConn(tctx) + if err != nil { + return err + } + + var tables []string + if err := db.Table("information_schema.tables").Select("table_name").Where("table_schema = ?", "public").Find(&tables).Error; err != nil { + return err + } + dropCount := 0 + for _, v := range tables { + if !strings.HasPrefix(v, tenantId) { + continue + } + if err := db.Exec(fmt.Sprintf(dropTableStmt, v)).Error; err != nil { + return err + } + dropCount++ + } + + logger.Infof("Deleted %d tables for %s", dropCount, request.TenantId) + return nil +} diff --git a/db/main.go b/db/main.go index 4bcb9a6..50c0776 100644 --- a/db/main.go +++ b/db/main.go @@ -2,6 +2,7 @@ package main import ( pb "github.com/micro/services/db/proto" + admin "github.com/micro/services/pkg/service/proto" "github.com/micro/services/pkg/tracing" "github.com/micro/services/db/handler" @@ -40,9 +41,7 @@ func main() { // Register handler pb.RegisterDbHandler(srv.Server(), h) - - // Register handler - pb.RegisterDbHandler(srv.Server(), &handler.Db{}) + admin.RegisterAdminHandler(srv.Server(), h) traceCloser := tracing.SetupOpentracing("db") defer traceCloser.Close() diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go new file mode 100644 index 0000000..8f8ab48 --- /dev/null +++ b/pkg/auth/auth.go @@ -0,0 +1,34 @@ +package auth + +import ( + "context" + + "github.com/micro/micro/v3/service/auth" + "github.com/micro/micro/v3/service/errors" +) + +func VerifyMicroAdmin(ctx context.Context, method string) (*auth.Account, error) { + acc, ok := auth.AccountFromContext(ctx) + if !ok { + return nil, errors.Unauthorized(method, "Unauthorized") + } + if err := doVerifyMicroAdmin(acc, method); err != nil { + return nil, err + } + return acc, nil +} + +func doVerifyMicroAdmin(acc *auth.Account, method string) error { + errForbid := errors.Forbidden(method, "Forbidden") + if acc.Issuer != "micro" { + return errForbid + } + + for _, s := range acc.Scopes { + if (s == "admin" && acc.Type == "user") || (s == "service" && acc.Type == "service") { + return nil + } + } + return errForbid + +} diff --git a/pkg/tenant/tenant.go b/pkg/tenant/tenant.go index 42080aa..9a2cd23 100644 --- a/pkg/tenant/tenant.go +++ b/pkg/tenant/tenant.go @@ -8,6 +8,10 @@ import ( "github.com/micro/micro/v3/service/auth" ) +const ( + metaOwner = "apikey_owner" +) + // FromContext returns a tenant from the context func FromContext(ctx context.Context) (string, bool) { acc, ok := auth.AccountFromContext(ctx) @@ -21,7 +25,7 @@ func FromContext(ctx context.Context) (string, bool) { func FromAccount(acc *auth.Account) string { id := acc.ID issuer := acc.Issuer - owner := acc.Metadata["apikey_owner"] + owner := acc.Metadata[metaOwner] if len(id) == 0 { id = "micro" @@ -47,3 +51,14 @@ func CreateKey(ctx context.Context, key string) string { // return a tenant prefixed key e.g micro/asim/foobar return fmt.Sprintf("%s/%s", t, key) } + +// NewContext returns a context that will encapsulate the given tenant +func NewContext(id, issuer, owner string) context.Context { + return auth.ContextWithAccount(context.Background(), &auth.Account{ + ID: id, + Issuer: issuer, + Metadata: map[string]string{ + metaOwner: owner, + }, + }) +} diff --git a/user/handler/handler.go b/user/handler/handler.go index c372939..a1935f5 100644 --- a/user/handler/handler.go +++ b/user/handler/handler.go @@ -10,10 +10,10 @@ import ( "time" "github.com/google/uuid" - "github.com/micro/micro/v3/service/auth" "github.com/micro/micro/v3/service/errors" "github.com/micro/micro/v3/service/logger" "github.com/micro/micro/v3/service/store" + pauth "github.com/micro/services/pkg/auth" adminpb "github.com/micro/services/pkg/service/proto" "golang.org/x/crypto/bcrypt" @@ -510,7 +510,7 @@ func (s *User) VerifyToken(ctx context.Context, req *pb.VerifyTokenRequest, rsp } func (s *User) DeleteData(ctx context.Context, request *adminpb.DeleteDataRequest, response *adminpb.DeleteDataResponse) error { - if _, err := verifyMicroAdmin(ctx, "user.DeleteData"); err != nil { + if _, err := pauth.VerifyMicroAdmin(ctx, "user.DeleteData"); err != nil { return err } @@ -519,29 +519,3 @@ func (s *User) DeleteData(ctx context.Context, request *adminpb.DeleteDataReques } return s.domain.DeleteTenantData(request.TenantId) } - -func verifyMicroAdmin(ctx context.Context, method string) (*auth.Account, error) { - acc, ok := auth.AccountFromContext(ctx) - if !ok { - return nil, errors.Unauthorized(method, "Unauthorized") - } - if err := doVerifyMicroAdmin(acc, method); err != nil { - return nil, err - } - return acc, nil -} - -func doVerifyMicroAdmin(acc *auth.Account, method string) error { - errForbid := errors.Forbidden(method, "Forbidden") - if acc.Issuer != "micro" { - return errForbid - } - - for _, s := range acc.Scopes { - if (s == "admin" && acc.Type == "user") || (s == "service" && acc.Type == "service") { - return nil - } - } - return errForbid - -}