[server] Use pgtype.UUID for User.Home

This commit is contained in:
Abhishek Shroff
2025-05-07 09:57:26 +05:30
parent e4ae23817c
commit a66024960d
15 changed files with 74 additions and 95 deletions

View File

@@ -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))
}
}

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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)
}
}
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
);

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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`

View File

@@ -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

View File

@@ -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