From 3ef436d8fabbf3da3b29073ec175d889cc1a3c26 Mon Sep 17 00:00:00 2001 From: Abhishek Shroff Date: Wed, 31 Jul 2024 09:59:04 -0700 Subject: [PATCH] List silos --- server/internal/api/api.go | 57 ++++-------------------- server/internal/api/auth.go | 51 +++++++++++++++++++++ server/internal/api/auth/auth_bearer.go | 23 +++++++--- server/internal/api/silo.go | 43 ++++++++++++++++++ server/internal/app/silo_manager.go | 16 +++++-- server/internal/app/storage_manager.go | 4 +- server/internal/command/silo.go | 6 +-- server/internal/core/silo.go | 20 ++++++--- server/internal/storage/local_storage.go | 17 ++++--- server/internal/storage/minio_storage.go | 8 +++- server/internal/storage/storage.go | 6 ++- 11 files changed, 174 insertions(+), 77 deletions(-) create mode 100644 server/internal/api/auth.go create mode 100644 server/internal/api/silo.go diff --git a/server/internal/api/api.go b/server/internal/api/api.go index 6102b1f0..b4fecf89 100644 --- a/server/internal/api/api.go +++ b/server/internal/api/api.go @@ -1,64 +1,25 @@ package api import ( - "errors" - "github.com/gin-gonic/gin" "github.com/shroff/phylum/server/internal/api/auth" "github.com/shroff/phylum/server/internal/app" ) -func Setup(r *gin.RouterGroup, app *app.App) { - setupAuthRoutes(r) - r.Use(auth.CreateBearerAuthHandler(app)) - setupResourceRoutes(r) +func Setup(r *gin.RouterGroup, a *app.App) { + // Unauthenticated routes + setupAuthRoutes(r, a) + + // Authenticated routes + r.Use(auth.CreateBearerAuthHandler(a)) + setupSiloRoutes(r, a) + setupResourceRoutes(r, a) } -func setupAuthRoutes(r *gin.RouterGroup) { - group := r.Group("/auth") - group.POST("/login", handleLoginRoute) -} - -func setupResourceRoutes(r *gin.RouterGroup) { +func setupResourceRoutes(r *gin.RouterGroup, a *app.App) { group := r.Group("/resources") group.GET("/:id/metadata") group.PUT("/:id/metadata") group.GET("/:id/contents") group.PUT("/:id/contents") } - -func handleLoginRoute(c *gin.Context) { - username, ok := c.GetQuery("username") - if !ok { - c.AbortWithStatusJSON(401, gin.H{ - "ERR_CODE": "missing_params", - "ERR_DETAILS": "Missing username input", - }) - return - } - password, ok := c.GetQuery("password") - if !ok { - c.AbortWithStatusJSON(401, gin.H{ - "ERR_CODE": "missing_params", - "ERR_DETAILS": "Missing password input", - }) - return - } - - if token, err := app.Default.CreateAccessToken(username, password); err != nil { - if errors.Is(err, app.ErrCredentialsInvalid) { - c.AbortWithStatusJSON(401, gin.H{ - "ERR_CODE": "invalid_username_or_password", - }) - } else { - c.AbortWithStatusJSON(401, gin.H{ - "ERR_DETAILS": err.Error(), - }) - } - } else { - c.JSON(200, gin.H{ - "access_token": token.ID, - "expires": token.Expires, - }) - } -} diff --git a/server/internal/api/auth.go b/server/internal/api/auth.go new file mode 100644 index 00000000..5d4732c9 --- /dev/null +++ b/server/internal/api/auth.go @@ -0,0 +1,51 @@ +package api + +import ( + "errors" + + "github.com/gin-gonic/gin" + "github.com/shroff/phylum/server/internal/app" +) + +func setupAuthRoutes(r *gin.RouterGroup, a *app.App) { + group := r.Group("/auth") + group.POST("/login", createLoginRouteHandler(a)) +} + +func createLoginRouteHandler(a *app.App) func(c *gin.Context) { + return func(c *gin.Context) { + username, ok := c.GetQuery("username") + if !ok { + c.AbortWithStatusJSON(401, gin.H{ + "ERR_CODE": "missing_params", + "ERR_DETAILS": "Missing username input", + }) + return + } + password, ok := c.GetQuery("password") + if !ok { + c.AbortWithStatusJSON(401, gin.H{ + "ERR_CODE": "missing_params", + "ERR_DETAILS": "Missing password input", + }) + return + } + + if token, err := a.CreateAccessToken(username, password); err != nil { + if errors.Is(err, app.ErrCredentialsInvalid) { + c.AbortWithStatusJSON(401, gin.H{ + "ERR_CODE": "invalid_username_or_password", + }) + } else { + c.AbortWithStatusJSON(401, gin.H{ + "ERR_DETAILS": err.Error(), + }) + } + } else { + c.JSON(200, gin.H{ + "access_token": token.ID, + "expires": token.Expires, + }) + } + } +} diff --git a/server/internal/api/auth/auth_bearer.go b/server/internal/api/auth/auth_bearer.go index 60deb5e2..ed4a8584 100644 --- a/server/internal/api/auth/auth_bearer.go +++ b/server/internal/api/auth/auth_bearer.go @@ -6,34 +6,43 @@ import ( "github.com/gin-gonic/gin" "github.com/shroff/phylum/server/internal/app" + "github.com/sirupsen/logrus" ) const keyUsername = "username" func CreateBearerAuthHandler(a *app.App) func(c *gin.Context) { return func(c *gin.Context) { - authHeader := strings.Split(c.GetHeader("Authorization"), " ") - if len(authHeader) != 2 { + authHeader := c.GetHeader("Authorization") + if authHeader == "" { c.AbortWithStatusJSON(401, gin.H{ - "ERR_CODE": "authorization_malformed", + "ERR_CODE": "auth_required", }) return } - if authHeader[0] != "bearer" { + authParts := strings.Split(authHeader, " ") + if len(authParts) != 2 { c.AbortWithStatusJSON(401, gin.H{ - "ERR_CODE": "authorization_malformed", + "ERR_CODE": "auth_malformed", + }) + return + } + if authParts[0] != "bearer" { + c.AbortWithStatusJSON(401, gin.H{ + "ERR_CODE": "auth_scheme_not_recognized", }) return } - username, err := a.VerifyAccessToken(authHeader[1]) + username, err := a.VerifyAccessToken(authParts[1]) if err != nil { if errors.Is(err, app.ErrAccessTokenExpired) || errors.Is(err, app.ErrAccessTokenInvalid) { c.AbortWithStatusJSON(401, gin.H{ "ERR_CODE": "access_token_invalid", }) } else { - c.AbortWithStatusJSON(401, gin.H{ + logrus.Warn(err.Error()) + c.AbortWithStatusJSON(500, gin.H{ "ERR_CODE": "unknown_error", "ERR_DETAILS": err.Error(), }) diff --git a/server/internal/api/silo.go b/server/internal/api/silo.go new file mode 100644 index 00000000..adfeb6ff --- /dev/null +++ b/server/internal/api/silo.go @@ -0,0 +1,43 @@ +package api + +import ( + "context" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/shroff/phylum/server/internal/app" +) + +type siloResponse struct { + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Owner string `json:"owner"` + Storage string `json:"storage"` +} + +func setupSiloRoutes(r *gin.RouterGroup, a *app.App) { + group := r.Group("/silos") + group.GET("/list", createSilosListRouteHandler(a)) +} + +func createSilosListRouteHandler(a *app.App) func(c *gin.Context) { + return func(c *gin.Context) { + silos, err := a.ListSilos(context.Background()) + if err != nil { + c.AbortWithStatusJSON(500, gin.H{ + "ERR_DETAILS": err.Error(), + }) + return + } + results := make([]siloResponse, len(silos)) + for i, s := range silos { + results[i] = siloResponse{ + ID: s.ID(), + Name: s.Name(), + Owner: s.Owner(), + Storage: s.StorageName(), + } + } + c.JSON(200, results) + } +} diff --git a/server/internal/app/silo_manager.go b/server/internal/app/silo_manager.go index 86e8e3f7..a2797feb 100644 --- a/server/internal/app/silo_manager.go +++ b/server/internal/app/silo_manager.go @@ -28,8 +28,16 @@ func (a App) CreateSilo(ctx context.Context, id uuid.UUID, owner, storage, name }) } -func (a App) ListSilos(ctx context.Context) ([]sql.Silo, error) { - return a.Db.Queries().ListSilos(ctx) +func (a App) ListSilos(ctx context.Context) ([]*core.Silo, error) { + silos, err := a.Db.Queries().ListSilos(ctx) + if err != nil { + return nil, err + } + results := make([]*core.Silo, len(silos)) + for i, s := range silos { + results[i] = core.NewSilo(a.Db, s.Name, s.Owner, s.ID, a.FindStorageBackend(s.Storage)) + } + return results, nil } func (a App) FindSilo(ctx context.Context, idOrName string) (*core.Silo, error) { @@ -52,7 +60,7 @@ func (a App) FindSilo(ctx context.Context, idOrName string) (*core.Silo, error) return nil, errors.New("storage backend not found for " + silo.Name + "(" + silo.ID.String() + ")") } - return core.NewSilo(a.Db, storage, silo.ID, silo.Name), nil + return core.NewSilo(a.Db, silo.Name, silo.Owner, silo.ID, storage), nil } func (a App) DeleteSilo(ctx context.Context, id uuid.UUID) error { @@ -65,7 +73,7 @@ func (a App) DeleteSilo(ctx context.Context, id uuid.UUID) error { return errors.New("storage backend not found for " + id.String()) } - silo := core.NewSilo(a.Db, storage, data.ID, data.Name) + silo := core.NewSilo(a.Db, data.Name, data.Owner, data.ID, storage) if err := silo.DeleteRecursive(ctx, id, true); err != nil { return err } diff --git a/server/internal/app/storage_manager.go b/server/internal/app/storage_manager.go index aea85873..c11e6504 100644 --- a/server/internal/app/storage_manager.go +++ b/server/internal/app/storage_manager.go @@ -15,7 +15,7 @@ func restoreStorageBackends(db *db.DbHandler) (map[string]storage.Storage, error } results := map[string]storage.Storage{} for _, b := range backends { - store, err := storage.Open(b.Driver, b.Params) + store, err := storage.Open(b.Name, b.Driver, b.Params) if err != nil { return nil, err } @@ -25,7 +25,7 @@ func restoreStorageBackends(db *db.DbHandler) (map[string]storage.Storage, error } func (a App) CreateStorageBackend(name string, driver string, params map[string]string) error { - storage, err := storage.Open(driver, params) + storage, err := storage.Open(name, driver, params) if err != nil { return nil } diff --git a/server/internal/command/silo.go b/server/internal/command/silo.go index 4b89031e..61e3a671 100644 --- a/server/internal/command/silo.go +++ b/server/internal/command/silo.go @@ -63,9 +63,9 @@ func setupSiloListCommand() *cobra.Command { logrus.Fatal(err) } for _, silo := range silos { - logrus.Infof("%-16s: %s\n", silo.Name, silo.ID.String()) - logrus.Infof(" storage: %s\n", silo.Storage) - logrus.Infof(" owner: %s\n", silo.Owner) + logrus.Infof("%-16s: %s\n", silo.Name(), silo.ID().String()) + logrus.Infof(" storage: %s\n", silo.StorageName()) + logrus.Infof(" owner: %s\n", silo.Owner()) logrus.Info() } }, diff --git a/server/internal/core/silo.go b/server/internal/core/silo.go index 84c73c76..86408212 100644 --- a/server/internal/core/silo.go +++ b/server/internal/core/silo.go @@ -16,17 +16,19 @@ import ( type Silo struct { db *db.DbHandler - storage storage.Storage - root uuid.UUID name string + owner string + root uuid.UUID + storage storage.Storage } -func NewSilo(db *db.DbHandler, storage storage.Storage, root uuid.UUID, name string) *Silo { +func NewSilo(db *db.DbHandler, name, owner string, root uuid.UUID, storage storage.Storage) *Silo { return &Silo{ db: db, - storage: storage, - root: root, name: name, + owner: owner, + root: root, + storage: storage, } } @@ -38,6 +40,14 @@ func (s *Silo) Name() string { return s.name } +func (s *Silo) Owner() string { + return s.owner +} + +func (s *Silo) StorageName() string { + return s.storage.Name() +} + func (s *Silo) OpenRead(ctx context.Context, id uuid.UUID, start, length int64) (io.ReadCloser, error) { return s.storage.OpenRead(id, start, length) } diff --git a/server/internal/storage/local_storage.go b/server/internal/storage/local_storage.go index c0742320..4ac15be1 100644 --- a/server/internal/storage/local_storage.go +++ b/server/internal/storage/local_storage.go @@ -13,9 +13,12 @@ import ( "github.com/shroff/phylum/server/internal/sql" ) -type localStorage string +type localStorage struct { + name string + root string +} -func newLocalStorage(root string) (Storage, error) { +func newLocalStorage(name, root string) (Storage, error) { if root == "" { return nil, errors.New("local storage root not provided") } @@ -23,7 +26,11 @@ func newLocalStorage(root string) (Storage, error) { if err != nil { return nil, err } - return localStorage(root), nil + return localStorage{name: name, root: root}, nil +} + +func (l localStorage) Name() string { + return l.name } func (l localStorage) OpenRead(id uuid.UUID, start, length int64) (io.ReadCloser, error) { @@ -66,9 +73,9 @@ func (l localStorage) Delete(rows []sql.HardDeleteRecursiveRow) []error { } func (l localStorage) path(id uuid.UUID) string { - return filepath.Join(string(l), id.String()) + return filepath.Join(string(l.root), id.String()) } func (l localStorage) String() string { - return "local (" + string(l) + ")" + return "local (" + string(l.root) + ")" } diff --git a/server/internal/storage/minio_storage.go b/server/internal/storage/minio_storage.go index a2fb69fd..b80ea13f 100644 --- a/server/internal/storage/minio_storage.go +++ b/server/internal/storage/minio_storage.go @@ -18,6 +18,7 @@ import ( ) type minioStorage struct { + name string client *minio.Client tmp string bucketName string @@ -25,6 +26,7 @@ type minioStorage struct { } func newMinioStorage( + name, endpoint, keyId, accessKey, @@ -57,7 +59,11 @@ func newMinioStorage( if err != nil { return nil, err } - return minioStorage{client: client, tmp: tmp, bucketName: bucketName, prefix: prefix}, nil + return minioStorage{name: name, client: client, tmp: tmp, bucketName: bucketName, prefix: prefix}, nil +} + +func (s minioStorage) Name() string { + return s.name } func (s minioStorage) OpenRead(id uuid.UUID, start, length int64) (io.ReadCloser, error) { diff --git a/server/internal/storage/storage.go b/server/internal/storage/storage.go index 2e53800b..a7567f13 100644 --- a/server/internal/storage/storage.go +++ b/server/internal/storage/storage.go @@ -9,18 +9,20 @@ import ( ) type Storage interface { + Name() string OpenRead(id uuid.UUID, start, length int64) (io.ReadCloser, error) OpenWrite(id uuid.UUID, callback func(int, string) error) (io.WriteCloser, error) Delete(rows []sql.HardDeleteRecursiveRow) []error String() string } -func Open(driver string, params map[string]string) (Storage, error) { +func Open(name string, driver string, params map[string]string) (Storage, error) { switch driver { case "local": - return newLocalStorage(params["root"]) + return newLocalStorage(name, params["root"]) case "minio": return newMinioStorage( + name, params["endpoint"], params["key_id"], params["access_key"],