diff --git a/server/internal/api/publink/publink.go b/server/internal/api/publink/publink.go index 2dd7f3b2..acbf26fa 100644 --- a/server/internal/api/publink/publink.go +++ b/server/internal/api/publink/publink.go @@ -15,7 +15,7 @@ func Setup(r *gin.RouterGroup) { path := c.Param("path") _, password, _ := c.Request.BasicAuth() - f, err := core.OpenFromPublink(c.Request.Context(), id, password) + f, err := core.OpenFileSystemFromPublink(c.Request.Context(), id, password) if err != nil { if errors.Is(err, core.ErrInsufficientPermissions) { c.Header("WWW-Authenticate", "Basic realm=\""+id+"\"") diff --git a/server/internal/core/fs.go b/server/internal/core/core.go similarity index 70% rename from server/internal/core/fs.go rename to server/internal/core/core.go index c953a84f..edeac364 100644 --- a/server/internal/core/fs.go +++ b/server/internal/core/core.go @@ -4,15 +4,12 @@ import ( "context" "errors" "io" - "strings" - "time" "github.com/doug-martin/goqu/v9" _ "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/crypt" "github.com/shroff/phylum/server/internal/db" "github.com/sirupsen/logrus" ) @@ -93,7 +90,7 @@ type FileSystem interface { TrashEmpty() (int, error) } -func Open(ctx context.Context, userID int32, root pgtype.UUID, fullAccess bool) FileSystem { +func OpenFileSystem(ctx context.Context, userID int32, root pgtype.UUID, fullAccess bool) FileSystem { return filesystem{ db: db.Get(ctx), userID: userID, @@ -102,6 +99,10 @@ func Open(ctx context.Context, userID int32, root pgtype.UUID, fullAccess bool) } } +func (u User) OpenFileSystem(ctx context.Context) FileSystem { + return OpenFileSystem(ctx, u.ID, u.Home, u.Permissions&PermissionFilesAll != 0) +} + func OpenOmniscient(db db.Handler) FileSystem { return filesystem{ db: db, @@ -139,64 +140,3 @@ func _readRootID(ctx context.Context) (uuid.UUID, error) { return id, nil } } - -func OpenFromPublink(ctx context.Context, id string, password string) (ROFileSystem, error) { - d := db.Get(ctx) - link, err := getPublink(d, id) - if err != nil { - return nil, err - } - - // check password - if link.PasswordHash != "" { - if password == "" { - return nil, ErrInsufficientPermissions - } - if ok, err := crypt.Verify(password, link.PasswordHash); err != nil { - return nil, err - } else if !ok { - return nil, ErrInsufficientPermissions - } - } - - // check expiration - if link.Expires != 0 && time.UnixMilli(link.Expires).Before(time.Now()) { - return nil, ErrResourceNotFound - } - - // check max accesses - if link.AccessLimit > 0 && link.AccessLimit <= link.Accessed { - return nil, ErrResourceNotFound - } - - const q = "UPDATE publinks SET accessed = accessed + 1 WHERE id = $1" - if _, err := d.Exec(q, link.ID); err != nil { - return nil, err - } - - return roFilesystem{f: filesystem{ - db: d, - pathRoot: pgtype.UUID{Bytes: link.Root, Valid: true}, - fullAccess: true, // TODO: #permissions Replace with permissions int - }}, nil -} - -func getPublink(d db.Handler, id string) (Publink, error) { - q := "SELECT * FROM publinks p WHERE id = $1::TEXT" - if rows, err := d.Query(q, id); err != nil { - return Publink{}, err - } else if link, err := pgx.CollectExactlyOneRow(rows, scanPublink); err != nil { - if errors.Is(err, pgx.ErrNoRows) { - err = ErrParentNotFound - } - return Publink{}, err - } else { - return link, nil - } -} - -func CheckNameInvalid(s string) bool { - return s == "" || s == "." || s == ".." || strings.ContainsFunc(s, func(r rune) bool { - return r == 0 || r == '/' - }) -} diff --git a/server/internal/core/publink.go b/server/internal/core/publink.go index d03bea5d..6182ceb6 100644 --- a/server/internal/core/publink.go +++ b/server/internal/core/publink.go @@ -20,32 +20,6 @@ type Publink struct { Expires int64 } -type ROFileSystem interface { - ResourceByPath(string) (Resource, error) - ReadDir(Resource, bool) ([]Resource, error) - FindVersion(Resource, uuid.UUID) (Version, error) - Walk(Resource, int, func(Resource, string) error) error -} - -type roFilesystem struct { - f filesystem -} - -func (f roFilesystem) ResourceByPath(path string) (Resource, error) { - return f.f.ResourceByPath(path) -} -func (f roFilesystem) ReadDir(r Resource, recursive bool) ([]Resource, error) { - return f.f.ReadDir(r, recursive) -} - -func (f roFilesystem) FindVersion(r Resource, versionID uuid.UUID) (Version, error) { - return f.f.FindVersion(r, versionID) -} - -func (f roFilesystem) Walk(r Resource, depth int, fn func(Resource, string) error) error { - return f.f.Walk(r, depth, fn) -} - func scanPublink(row pgx.CollectableRow) (Publink, error) { var p Publink var expires *time.Time diff --git a/server/internal/core/publink_fs.go b/server/internal/core/publink_fs.go new file mode 100644 index 00000000..b4803f4f --- /dev/null +++ b/server/internal/core/publink_fs.go @@ -0,0 +1,94 @@ +package core + +import ( + "context" + "errors" + "time" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" + "github.com/shroff/phylum/server/internal/crypt" + "github.com/shroff/phylum/server/internal/db" +) + +type ROFileSystem interface { + ResourceByPath(string) (Resource, error) + ReadDir(Resource, bool) ([]Resource, error) + FindVersion(Resource, uuid.UUID) (Version, error) + Walk(Resource, int, func(Resource, string) error) error +} + +type roFilesystem struct { + f filesystem +} + +func (f roFilesystem) ResourceByPath(path string) (Resource, error) { + return f.f.ResourceByPath(path) +} +func (f roFilesystem) ReadDir(r Resource, recursive bool) ([]Resource, error) { + return f.f.ReadDir(r, recursive) +} + +func (f roFilesystem) FindVersion(r Resource, versionID uuid.UUID) (Version, error) { + return f.f.FindVersion(r, versionID) +} + +func (f roFilesystem) Walk(r Resource, depth int, fn func(Resource, string) error) error { + return f.f.Walk(r, depth, fn) +} + +func OpenFileSystemFromPublink(ctx context.Context, id string, password string) (ROFileSystem, error) { + d := db.Get(ctx) + link, err := getPublink(d, id) + if err != nil { + return nil, err + } + + // check password + if link.PasswordHash != "" { + if password == "" { + return nil, ErrInsufficientPermissions + } + if ok, err := crypt.Verify(password, link.PasswordHash); err != nil { + return nil, err + } else if !ok { + return nil, ErrInsufficientPermissions + } + } + + // check expiration + if link.Expires != 0 && time.UnixMilli(link.Expires).Before(time.Now()) { + return nil, ErrResourceNotFound + } + + // check max accesses + if link.AccessLimit > 0 && link.AccessLimit <= link.Accessed { + return nil, ErrResourceNotFound + } + + const q = "UPDATE publinks SET accessed = accessed + 1 WHERE id = $1" + if _, err := d.Exec(q, link.ID); err != nil { + return nil, err + } + + return roFilesystem{f: filesystem{ + db: d, + pathRoot: pgtype.UUID{Bytes: link.Root, Valid: true}, + fullAccess: true, // TODO: #permissions Replace with permissions int + }}, nil +} + +func getPublink(d db.Handler, id string) (Publink, error) { + q := "SELECT * FROM publinks p WHERE id = $1::TEXT" + if rows, err := d.Query(q, id); err != nil { + return Publink{}, err + } else if link, err := pgx.CollectExactlyOneRow(rows, scanPublink); err != nil { + if errors.Is(err, pgx.ErrNoRows) { + err = ErrParentNotFound + } + return Publink{}, err + } else { + return link, nil + } +} diff --git a/server/internal/core/user.go b/server/internal/core/user.go index 762a3295..ecf7e52c 100644 --- a/server/internal/core/user.go +++ b/server/internal/core/user.go @@ -31,10 +31,6 @@ func scanUser(row pgx.CollectableRow) (User, error) { return u, err } -func (u User) OpenFileSystem(ctx context.Context) FileSystem { - return Open(ctx, u.ID, u.Home, u.Permissions&PermissionFilesAll != 0) -} - type Manager interface { // create.go CreateUser(email, name string, noCreateHome bool) (User, error) diff --git a/server/internal/core/util.go b/server/internal/core/util.go index bb1d4da8..d37fb6bb 100644 --- a/server/internal/core/util.go +++ b/server/internal/core/util.go @@ -4,6 +4,7 @@ import ( "crypto/sha256" "hash" "io" + "strings" "github.com/gabriel-vasile/mimetype" ) @@ -52,3 +53,9 @@ func (c *contentPropsComputer) Close() error { } return c.successCallback(c.len, c.sum, mimetype.Detect(c.contents).String()) } + +func CheckNameInvalid(s string) bool { + return s == "" || s == "." || s == ".." || strings.ContainsFunc(s, func(r rune) bool { + return r == 0 || r == '/' + }) +}