mirror of
https://github.com/kevin-DL/services.git
synced 2026-01-14 20:14:47 +00:00
make multi tenancy based on namespace and ID of account (#85)
* make multi tenancy based on namespace and ID of account not just namespace * fix tests
This commit is contained in:
@@ -4,11 +4,12 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/micro/micro/v3/service/auth"
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
"github.com/micro/micro/v3/service/events"
|
||||
gorm2 "github.com/micro/services/pkg/gorm"
|
||||
|
||||
"github.com/nats-io/nats-streaming-server/util"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -26,18 +27,21 @@ type Token struct {
|
||||
Token string `gorm:"primaryKey"`
|
||||
Topic string
|
||||
ExpiresAt time.Time
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type Streams struct {
|
||||
DB *gorm.DB
|
||||
gorm2.Helper
|
||||
Events events.Stream
|
||||
Time func() time.Time
|
||||
}
|
||||
|
||||
// fmtTopic returns a topic string with namespace prefix
|
||||
func fmtTopic(ns, topic string) string {
|
||||
return fmt.Sprintf("%s.%s", ns, topic)
|
||||
func fmtTopic(acc *auth.Account, topic string) string {
|
||||
owner := acc.Metadata["apikey_owner"]
|
||||
if len(owner) == 0 {
|
||||
owner = acc.ID
|
||||
}
|
||||
return fmt.Sprintf("%s.%s.%s", acc.Issuer, owner, topic)
|
||||
}
|
||||
|
||||
// validateTopicInput validates that topic is alphanumeric
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package handler_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micro/micro/v3/service/events"
|
||||
"github.com/micro/services/streams/handler"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func testHandler(t *testing.T) *handler.Streams {
|
||||
@@ -17,23 +16,24 @@ func testHandler(t *testing.T) *handler.Streams {
|
||||
if len(addr) == 0 {
|
||||
addr = "postgresql://postgres@localhost:5432/postgres?sslmode=disable"
|
||||
}
|
||||
db, err := gorm.Open(postgres.Open(addr), &gorm.Config{})
|
||||
|
||||
sqlDB, err := sql.Open("pgx", addr)
|
||||
if err != nil {
|
||||
t.Fatalf("Error connecting to database: %v", err)
|
||||
t.Fatalf("Failed to open connection to DB %s", err)
|
||||
}
|
||||
// clean any data from a previous run
|
||||
if _, err := sqlDB.Exec("DROP TABLE IF EXISTS micro_users, micro_tokens CASCADE"); err != nil {
|
||||
t.Fatalf("Error cleaning database: %v", err)
|
||||
}
|
||||
|
||||
// migrate the database
|
||||
if err := db.AutoMigrate(&handler.Token{}); err != nil {
|
||||
t.Fatalf("Error migrating database: %v", err)
|
||||
}
|
||||
|
||||
return &handler.Streams{
|
||||
DB: db,
|
||||
h := &handler.Streams{
|
||||
Events: new(eventsMock),
|
||||
Time: func() time.Time {
|
||||
return time.Unix(1612787045, 0)
|
||||
},
|
||||
}
|
||||
h.DBConn(sqlDB).Migrations(&handler.Token{})
|
||||
return h
|
||||
}
|
||||
|
||||
type eventsMock struct {
|
||||
|
||||
@@ -27,5 +27,5 @@ func (s *Streams) Publish(ctx context.Context, req *pb.Message, rsp *pb.PublishR
|
||||
|
||||
// publish the message
|
||||
logger.Infof("Publishing message to topic: %v", req.Topic)
|
||||
return s.Events.Publish(fmtTopic(acc.Issuer, req.Topic), req.Message)
|
||||
return s.Events.Publish(fmtTopic(acc, req.Topic), req.Message)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestPublish(t *testing.T) {
|
||||
|
||||
t.Run("ValidMessage", func(t *testing.T) {
|
||||
h := testHandler(t)
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo"})
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo", ID: "foo-id"})
|
||||
err := h.Publish(ctx, &pb.Message{
|
||||
Topic: topic, Message: msg,
|
||||
}, &pb.PublishResponse{})
|
||||
@@ -42,6 +42,6 @@ func TestPublish(t *testing.T) {
|
||||
assert.Equal(t, 1, h.Events.(*eventsMock).PublishCount)
|
||||
assert.Equal(t, msg, h.Events.(*eventsMock).PublishMessage)
|
||||
// topic is prefixed with acc issuer to implement multitenancy
|
||||
assert.Equal(t, "foo."+topic, h.Events.(*eventsMock).PublishTopic)
|
||||
assert.Equal(t, "foo.foo-id."+topic, h.Events.(*eventsMock).PublishTopic)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -34,7 +34,12 @@ func (s *Streams) Subscribe(ctx context.Context, req *pb.SubscribeRequest, strea
|
||||
|
||||
// find the token and check to see if it has expired
|
||||
var token Token
|
||||
if err := s.DB.Where(&Token{Token: req.Token, Namespace: acc.Issuer}).First(&token).Error; err == gorm.ErrRecordNotFound {
|
||||
dbConn, err := s.GetDBConn(ctx)
|
||||
if err != nil {
|
||||
logger.Errorf("Error reading token from store: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error reading token from database")
|
||||
}
|
||||
if err := dbConn.Where(&Token{Token: req.Token}).First(&token).Error; err == gorm.ErrRecordNotFound {
|
||||
return ErrInvalidToken
|
||||
} else if err != nil {
|
||||
logger.Errorf("Error reading token from store: %v", err)
|
||||
@@ -51,7 +56,7 @@ func (s *Streams) Subscribe(ctx context.Context, req *pb.SubscribeRequest, strea
|
||||
|
||||
// start the subscription
|
||||
logger.Infof("Subscribing to %v via queue %v", req.Topic, token.Token)
|
||||
evChan, err := s.Events.Consume(fmtTopic(acc.Issuer, req.Topic), events.WithGroup(token.Token))
|
||||
evChan, err := s.Events.Consume(fmtTopic(acc, req.Topic), events.WithGroup(token.Token))
|
||||
if err != nil {
|
||||
logger.Errorf("Error connecting to events stream: %v", err)
|
||||
return errors.InternalServerError("EVENTS_ERROR", "Error connecting to events stream")
|
||||
|
||||
@@ -108,7 +108,7 @@ func TestSubscribe(t *testing.T) {
|
||||
h.Events.(*eventsMock).ConsumeChan = c
|
||||
|
||||
var tRsp pb.TokenResponse
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{Issuer: "foo"})
|
||||
ctx := auth.ContextWithAccount(context.TODO(), &auth.Account{ID: "foo", Issuer: "my-ns"})
|
||||
err := h.Token(ctx, &pb.TokenRequest{
|
||||
Topic: "helloworld",
|
||||
}, &tRsp)
|
||||
@@ -158,7 +158,7 @@ func TestSubscribe(t *testing.T) {
|
||||
close(c)
|
||||
wg.Wait()
|
||||
assert.NoError(t, subsErr)
|
||||
assert.Equal(t, "foo.helloworld", h.Events.(*eventsMock).ConsumeTopic)
|
||||
assert.Equal(t, "my-ns.foo.helloworld", h.Events.(*eventsMock).ConsumeTopic)
|
||||
|
||||
// sleep to wait for the subscribe loop to push the message to the stream
|
||||
//time.Sleep(1 * time.Second)
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func (s *Streams) Token(ctx context.Context, req *pb.TokenRequest, rsp *pb.TokenResponse) error {
|
||||
acc, ok := auth.AccountFromContext(ctx)
|
||||
_, ok := auth.AccountFromContext(ctx)
|
||||
if !ok {
|
||||
return errors.Unauthorized("UNAUTHORIZED", "Unauthorized")
|
||||
}
|
||||
@@ -26,9 +26,13 @@ func (s *Streams) Token(ctx context.Context, req *pb.TokenRequest, rsp *pb.Token
|
||||
Token: uuid.New().String(),
|
||||
ExpiresAt: s.Time().Add(TokenTTL),
|
||||
Topic: req.Topic,
|
||||
Namespace: acc.Issuer,
|
||||
}
|
||||
if err := s.DB.Create(&t).Error; err != nil {
|
||||
dbConn, err := s.GetDBConn(ctx)
|
||||
if err != nil {
|
||||
logger.Errorf("Error creating token in store: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error writing token to database")
|
||||
}
|
||||
if err := dbConn.Create(&t).Error; err != nil {
|
||||
logger.Errorf("Error creating token in store: %v", err)
|
||||
return errors.InternalServerError("DATABASE_ERROR", "Error writing token to database")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user