From a66024960d533be0ea9be341c470a3a6fb2562f3 Mon Sep 17 00:00:00 2001 From: Abhishek Shroff Date: Wed, 7 May 2025 09:57:26 +0530 Subject: [PATCH] [server] Use pgtype.UUID for User.Home --- .../api/authenticator/authenticator.go | 2 +- server/internal/api/v1/auth/routes.go | 8 ++-- server/internal/api/v1/my/routes.go | 2 +- server/internal/api/webdav/auth.go | 30 ------------- server/internal/api/webdav/handler.go | 38 +++++++++-------- server/internal/command/common/common.go | 2 +- server/internal/command/user/add.go | 42 +++++++++---------- server/internal/command/user/mod.go | 11 +++-- .../core/db/migrations/data/002_users.sql | 2 +- server/internal/core/fs/fs.go | 3 +- server/internal/core/fs/shared.go | 4 +- server/internal/core/user/auth.go | 3 +- server/internal/core/user/create.go | 4 +- server/internal/core/user/update.go | 4 +- server/internal/core/user/user.go | 14 ++++--- 15 files changed, 74 insertions(+), 95 deletions(-) delete mode 100644 server/internal/api/webdav/auth.go diff --git a/server/internal/api/authenticator/authenticator.go b/server/internal/api/authenticator/authenticator.go index 80d0decf..7a4715be 100644 --- a/server/internal/api/authenticator/authenticator.go +++ b/server/internal/api/authenticator/authenticator.go @@ -38,7 +38,7 @@ func Require(c *gin.Context) { panic(err) } else { c.Set(keyUser, u) - c.Set(keyFileSystem, u.OpenFileSystem(ctx)) + c.Set(keyFileSystem, u.OpenHomeFileSystem(ctx)) } } diff --git a/server/internal/api/v1/auth/routes.go b/server/internal/api/v1/auth/routes.go index e1a6d0b9..5c13c560 100644 --- a/server/internal/api/v1/auth/routes.go +++ b/server/internal/api/v1/auth/routes.go @@ -4,7 +4,7 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" "github.com/shroff/phylum/server/internal/api/authenticator" "github.com/shroff/phylum/server/internal/core/errors" "github.com/shroff/phylum/server/internal/core/user" @@ -15,9 +15,9 @@ type loginResponse struct { User loggedInUserResponse `json:"user"` } type loggedInUserResponse struct { - Username string `json:"username"` - Display string `json:"display"` - Home uuid.UUID `json:"home"` + Username string `json:"username"` + Display string `json:"display"` + Home pgtype.UUID `json:"home"` } func SetupRoutes(r *gin.RouterGroup) { diff --git a/server/internal/api/v1/my/routes.go b/server/internal/api/v1/my/routes.go index 13405c94..5f2ef8aa 100644 --- a/server/internal/api/v1/my/routes.go +++ b/server/internal/api/v1/my/routes.go @@ -30,7 +30,7 @@ func SetupRoutes(r *gin.RouterGroup) { func handleHomeRoute(c *gin.Context) { user := authenticator.GetUser(c) f := authenticator.GetFileSystem(c) - r, err := f.ResourceByID(user.Home) + r, err := f.ResourceByPath("") if err != nil { panic(err) } diff --git a/server/internal/api/webdav/auth.go b/server/internal/api/webdav/auth.go deleted file mode 100644 index bf1a0dc5..00000000 --- a/server/internal/api/webdav/auth.go +++ /dev/null @@ -1,30 +0,0 @@ -package webdav - -import ( - "errors" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/shroff/phylum/server/internal/core/user" -) - -const keyFileSystem = "filesystem" - -func createBasicAuthHandler() func(c *gin.Context) { - return func(c *gin.Context) { - authSuccess := false - if username, pass, ok := c.Request.BasicAuth(); ok { - ctx := c.Request.Context() - if u, err := user.ManagerFromContext(ctx).VerifyUserPassword(username, pass); err == nil { - c.Set(keyFileSystem, u.OpenFileSystem(ctx)) - authSuccess = true - } else if !errors.Is(err, user.ErrCredentialsInvalid) { - panic(err) - } - } - if !authSuccess { - c.Header("WWW-Authenticate", "Basic realm=\"Phylum WebDAV\"") - c.AbortWithStatus(http.StatusUnauthorized) - } - } -} diff --git a/server/internal/api/webdav/handler.go b/server/internal/api/webdav/handler.go index a85c8674..c5647abb 100644 --- a/server/internal/api/webdav/handler.go +++ b/server/internal/api/webdav/handler.go @@ -1,11 +1,13 @@ package webdav import ( + "errors" "net/http" "github.com/gin-gonic/gin" webdav "github.com/shroff/phylum/server/internal/api/webdav/impl" "github.com/shroff/phylum/server/internal/core/fs" + "github.com/shroff/phylum/server/internal/core/user" "github.com/sirupsen/logrus" ) @@ -20,29 +22,29 @@ func SetupHandler(r *gin.RouterGroup) { prefix: r.BasePath(), lockSystem: webdav.NewMemLS(), } - r.Use(createBasicAuthHandler()) - r.Handle("OPTIONS", "*path", handler.HandleRequest) - r.Handle("GET", "*path", handler.HandleRequest) - r.Handle("PUT", "*path", handler.HandleRequest) - r.Handle("HEAD", "*path", handler.HandleRequest) - r.Handle("POST", "*path", handler.HandleRequest) - r.Handle("DELETE", "*path", handler.HandleRequest) - r.Handle("MOVE", "*path", handler.HandleRequest) - r.Handle("COPY", "*path", handler.HandleRequest) - r.Handle("MKCOL", "*path", handler.HandleRequest) - r.Handle("PROPFIND", "*path", handler.HandleRequest) - r.Handle("PROPPATCH", "*path", handler.HandleRequest) - r.Handle("LOCK", "*path", handler.HandleRequest) - r.Handle("UNLOCK", "*path", handler.HandleRequest) + r.Any("/:user/*path", handler.HandleRequest) } func (h *handler) HandleRequest(c *gin.Context) { - val, ok := c.Get(keyFileSystem) - if !ok { - c.AbortWithStatus(http.StatusInternalServerError) + authSuccess := false + var f fs.FileSystem + if username, pass, ok := c.Request.BasicAuth(); ok { + ctx := c.Request.Context() + if u, err := user.ManagerFromContext(ctx).VerifyUserPassword(username, pass); err == nil { + // TODO: consider allowing opening other users' home folders with permission checks + if u.Username == c.Param("user") { + f = u.OpenHomeFileSystem(ctx) + authSuccess = true + } + } else if !errors.Is(err, user.ErrCredentialsInvalid) { + panic(err) + } + } + if !authSuccess { + c.Header("WWW-Authenticate", "Basic realm=\"Phylum WebDAV\"") + c.AbortWithStatus(http.StatusUnauthorized) return } - f := val.(fs.FileSystem) webdavHandler := webdav.Handler{ Prefix: h.prefix, diff --git a/server/internal/command/common/common.go b/server/internal/command/common/common.go index 5a4d53df..d7501050 100644 --- a/server/internal/command/common/common.go +++ b/server/internal/command/common/common.go @@ -42,7 +42,7 @@ func UserFileSystem(cmd *cobra.Command) fs.FileSystem { if user == nil { f = fs.OpenOmniscient(context.Background()) } else { - f = user.OpenFileSystem(context.Background()) + f = user.OpenHomeFileSystem(context.Background()) } } return f diff --git a/server/internal/command/user/add.go b/server/internal/command/user/add.go index 5ba85a53..9fa6b650 100644 --- a/server/internal/command/user/add.go +++ b/server/internal/command/user/add.go @@ -7,6 +7,8 @@ import ( "strings" "syscall" + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" "github.com/shroff/phylum/server/internal/command/common" "github.com/shroff/phylum/server/internal/core/db" "github.com/shroff/phylum/server/internal/core/fs" @@ -61,19 +63,6 @@ func setupUserAddCommand() *cobra.Command { } f := common.RootFileSystem() - root := f.RootID() - if rootPathOrUUID, err := cmd.Flags().GetString("chroot"); err != nil { - fmt.Println("invalid value for flag 'chroot': " + err.Error()) - os.Exit(2) - } else if rootPathOrUUID != "" { - if r, err := f.ResourceByPathOrUUID(rootPathOrUUID); err != nil { - fmt.Println("invalid value for flag 'chroot': " + err.Error()) - os.Exit(1) - } else { - root = r.ID() - } - } - noCreateHome, _ := cmd.Flags().GetBool("no-create-home") homePath := "" if !noCreateHome { @@ -89,22 +78,33 @@ func setupUserAddCommand() *cobra.Command { err = db.Get(context.Background()).RunInTx(func(db db.Handler) error { userManager := user.ManagerFromDB(db) var u user.User - if user, err := userManager.CreateUser(username, displayName, password, root); err != nil { + var home fs.Resource + if homePath != "" { + f = common.RootFileSystem().WithDb(db) + var err error + home, err = f.CreateResourceByPath(homePath, true, true, fs.ResourceBindConflictResolutionEnsure) + if err != nil { + return err + } + } + + var homeID pgtype.UUID + if home.ID() != uuid.Nil { + homeID = pgtype.UUID{ + Bytes: home.ID(), + Valid: true, + } + } + if user, err := userManager.CreateUser(username, displayName, password, homeID); err != nil { return err } else { u = user } - if homePath != "" { - f = f.WithDb(db) - home, err := f.CreateResourceByPath(homePath, true, true, fs.ResourceBindConflictResolutionEnsure) - if err != nil { - return err - } + if homeID.Valid { if _, err := home.UpdatePermissions(u.Username, fs.PermissionRead|fs.PermissionWrite|fs.PermissionShare); err != nil { return err } - return userManager.UpdateUserHome(u, home.ID()) } return nil diff --git a/server/internal/command/user/mod.go b/server/internal/command/user/mod.go index 3e2d2336..88ef995d 100644 --- a/server/internal/command/user/mod.go +++ b/server/internal/command/user/mod.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" "github.com/shroff/phylum/server/internal/command/common" "github.com/shroff/phylum/server/internal/core/db" "github.com/shroff/phylum/server/internal/core/user" @@ -28,7 +28,7 @@ func setupUserModCommand() *cobra.Command { displayName, _ := cmd.Flags().GetString("name") rootFS := common.RootFileSystem() - homeID := uuid.Nil + var homeID pgtype.UUID if pathOrUUID, err := cmd.Flags().GetString("home"); err != nil { fmt.Println("invalid value for flag 'home': " + err.Error()) os.Exit(1) @@ -37,7 +37,10 @@ func setupUserModCommand() *cobra.Command { fmt.Println("invalid value for flag 'home': " + err.Error()) os.Exit(1) } else { - homeID = r.ID() + homeID = pgtype.UUID{ + Bytes: r.ID(), + Valid: true, + } } } @@ -48,7 +51,7 @@ func setupUserModCommand() *cobra.Command { return err } } - if homeID != uuid.Nil { + if homeID.Valid { if err := m.UpdateUserHome(u, homeID); err != nil { return err } diff --git a/server/internal/core/db/migrations/data/002_users.sql b/server/internal/core/db/migrations/data/002_users.sql index 4e0c1454..01604b23 100644 --- a/server/internal/core/db/migrations/data/002_users.sql +++ b/server/internal/core/db/migrations/data/002_users.sql @@ -5,7 +5,7 @@ CREATE TABLE users( deleted TIMESTAMP, display_name TEXT NOT NULL, password_hash TEXT NOT NULL, - home uuid NOT NULL REFERENCES resources(id), + home uuid REFERENCES resources(id), permissions INTEGER NOT NULL DEFAULT 0 ); diff --git a/server/internal/core/fs/fs.go b/server/internal/core/fs/fs.go index 9b609b19..d4f5de20 100644 --- a/server/internal/core/fs/fs.go +++ b/server/internal/core/fs/fs.go @@ -10,6 +10,7 @@ import ( _ "github.com/doug-martin/goqu/v9/dialect/postgres" "github.com/google/uuid" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" "github.com/shroff/phylum/server/internal/core/db" "github.com/shroff/phylum/server/internal/core/storage" "github.com/shroff/phylum/server/internal/core/util/crypt" @@ -53,7 +54,7 @@ type FileSystem interface { Search(query string, includeDeleted bool) ([]Resource, error) // shared.go - SharedResources(excluded uuid.UUID) ([]Resource, error) + SharedResources(excluded pgtype.UUID) ([]Resource, error) // trash.go TrashList(cursor string, n uint) ([]Resource, string, error) diff --git a/server/internal/core/fs/shared.go b/server/internal/core/fs/shared.go index 564b4334..439ac372 100644 --- a/server/internal/core/fs/shared.go +++ b/server/internal/core/fs/shared.go @@ -1,11 +1,11 @@ package fs import ( - "github.com/google/uuid" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" ) -func (f filesystem) SharedResources(excluded uuid.UUID) ([]Resource, error) { +func (f filesystem) SharedResources(excluded pgtype.UUID) ([]Resource, error) { const q = `SELECT r.*, (SELECT ` + publinkFieldsQuery + ` FROM publinks l WHERE l.root = r.id) as links FROM resources r WHERE grants ? $1::TEXT AND r.id != $2::UUID diff --git a/server/internal/core/user/auth.go b/server/internal/core/user/auth.go index e10655c3..8d3b84f2 100644 --- a/server/internal/core/user/auth.go +++ b/server/internal/core/user/auth.go @@ -5,7 +5,6 @@ import ( "time" "unsafe" - "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" "github.com/shroff/phylum/server/internal/core/errors" @@ -58,7 +57,7 @@ func (m manager) ReadAccessToken(accessToken string) (User, error) { var username string var displayName string var permissions int32 - var home uuid.UUID + var home pgtype.UUID if err := row.Scan(&expires, &username, &displayName, &permissions, &home); err != nil { if errors.Is(err, pgx.ErrNoRows) { err = ErrCredentialsInvalid diff --git a/server/internal/core/user/create.go b/server/internal/core/user/create.go index 24bad174..8b2ea33e 100644 --- a/server/internal/core/user/create.go +++ b/server/internal/core/user/create.go @@ -4,15 +4,15 @@ import ( "net/http" "strings" - "github.com/google/uuid" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" "github.com/shroff/phylum/server/internal/core/errors" "github.com/shroff/phylum/server/internal/core/util/crypt" ) var ErrUserExists = errors.NewError(http.StatusNotFound, "user_already_exists", "user already exists") -func (m manager) CreateUser(username, displayName, password string, home uuid.UUID) (User, error) { +func (m manager) CreateUser(username, displayName, password string, home pgtype.UUID) (User, error) { const q = ` INSERT INTO users(username, display_name, password_hash, home) VALUES ($1, $2, $3, $4) RETURNING username, display_name, password_hash, home, permissions` diff --git a/server/internal/core/user/update.go b/server/internal/core/user/update.go index 84244339..a00f89c8 100644 --- a/server/internal/core/user/update.go +++ b/server/internal/core/user/update.go @@ -1,11 +1,11 @@ package user import ( - "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" "github.com/shroff/phylum/server/internal/core/util/crypt" ) -func (m manager) UpdateUserHome(user User, home uuid.UUID) error { +func (m manager) UpdateUserHome(user User, home pgtype.UUID) error { const q = "UPDATE users SET home = $2::UUID, modified = NOW() WHERE username = $1::TEXT" if _, err := m.db.Exec(q, user.Username, home); err != nil { return err diff --git a/server/internal/core/user/user.go b/server/internal/core/user/user.go index e24ca509..d6172d67 100644 --- a/server/internal/core/user/user.go +++ b/server/internal/core/user/user.go @@ -6,6 +6,7 @@ import ( "github.com/google/uuid" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" "github.com/shroff/phylum/server/internal/core/fs" ) @@ -17,7 +18,7 @@ type BasicUser struct { type User struct { BasicUser PasswordHash string - Home uuid.UUID + Home pgtype.UUID Permissions int32 } @@ -42,20 +43,23 @@ func scanUser(row pgx.CollectableRow) (User, error) { return u, err } -func (u User) OpenFileSystem(ctx context.Context) fs.FileSystem { - return fs.Open(ctx, u.Username, u.Home, u.Permissions&PermissionAllFiles != 0) +func (u User) OpenHomeFileSystem(ctx context.Context) fs.FileSystem { + if !u.Home.Valid { + return nil + } + return fs.Open(ctx, u.Username, u.Home.Bytes, u.Permissions&PermissionAllFiles != 0) } type Manager interface { // create.go - CreateUser(username, displayName, password string, home uuid.UUID) (User, error) + CreateUser(username, displayName, password string, home pgtype.UUID) (User, error) // select.go ListUsers(since *time.Time) ([]BasicUser, error) UserByEmail(email string) (User, error) // update.go - UpdateUserHome(user User, home uuid.UUID) error + UpdateUserHome(user User, home pgtype.UUID) error UpdateUserDisplayName(user User, displayName string) error UpdateUserPassword(user User, password string) error