mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Auth: Refactor users path configuration and base path default
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -121,6 +121,22 @@ export class User extends RestModel {
|
|||||||
return s[0].trim();
|
return s[0].trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defaultBasePath() {
|
||||||
|
const handle = this.getHandle();
|
||||||
|
|
||||||
|
if (!handle) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
let dir = config.get("usersPath");
|
||||||
|
|
||||||
|
if (dir) {
|
||||||
|
return `${dir}/${handle}`;
|
||||||
|
} else {
|
||||||
|
return `users/${handle}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getDisplayName() {
|
getDisplayName() {
|
||||||
if (this.DisplayName) {
|
if (this.DisplayName) {
|
||||||
return this.DisplayName;
|
return this.DisplayName;
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ func StartImport(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
RemoveFromFolderCache(entity.RootImport)
|
RemoveFromFolderCache(entity.RootImport)
|
||||||
|
|
||||||
|
// Get destination folder.
|
||||||
var destFolder string
|
var destFolder string
|
||||||
if destFolder = s.User().GetUploadPath(); destFolder == "" {
|
if destFolder = s.User().GetUploadPath(); destFolder == "" {
|
||||||
destFolder = conf.ImportDest()
|
destFolder = conf.ImportDest()
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ func ProcessUserUpload(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
imp := get.Import()
|
imp := get.Import()
|
||||||
|
|
||||||
|
// Get destination folder.
|
||||||
var destFolder string
|
var destFolder string
|
||||||
if destFolder = s.User().GetUploadPath(); destFolder == "" {
|
if destFolder = s.User().GetUploadPath(); destFolder == "" {
|
||||||
destFolder = conf.ImportDest()
|
destFolder = conf.ImportDest()
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ type ClientConfig struct {
|
|||||||
UploadNSFW bool `json:"uploadNSFW"`
|
UploadNSFW bool `json:"uploadNSFW"`
|
||||||
Public bool `json:"public"`
|
Public bool `json:"public"`
|
||||||
AuthMode string `json:"authMode"`
|
AuthMode string `json:"authMode"`
|
||||||
|
UsersPath string `json:"usersPath"`
|
||||||
LoginUri string `json:"loginUri"`
|
LoginUri string `json:"loginUri"`
|
||||||
RegisterUri string `json:"registerUri"`
|
RegisterUri string `json:"registerUri"`
|
||||||
PasswordLength int `json:"passwordLength"`
|
PasswordLength int `json:"passwordLength"`
|
||||||
@@ -278,6 +279,7 @@ func (c *Config) ClientPublic() ClientConfig {
|
|||||||
ReadOnly: c.ReadOnly(),
|
ReadOnly: c.ReadOnly(),
|
||||||
Public: c.Public(),
|
Public: c.Public(),
|
||||||
AuthMode: c.AuthMode(),
|
AuthMode: c.AuthMode(),
|
||||||
|
UsersPath: c.UsersPath(),
|
||||||
LoginUri: c.LoginUri(),
|
LoginUri: c.LoginUri(),
|
||||||
RegisterUri: c.RegisterUri(),
|
RegisterUri: c.RegisterUri(),
|
||||||
PasswordResetUri: c.PasswordResetUri(),
|
PasswordResetUri: c.PasswordResetUri(),
|
||||||
@@ -364,6 +366,7 @@ func (c *Config) ClientShare() ClientConfig {
|
|||||||
UploadNSFW: c.UploadNSFW(),
|
UploadNSFW: c.UploadNSFW(),
|
||||||
Public: c.Public(),
|
Public: c.Public(),
|
||||||
AuthMode: c.AuthMode(),
|
AuthMode: c.AuthMode(),
|
||||||
|
UsersPath: "",
|
||||||
LoginUri: c.LoginUri(),
|
LoginUri: c.LoginUri(),
|
||||||
RegisterUri: c.RegisterUri(),
|
RegisterUri: c.RegisterUri(),
|
||||||
PasswordResetUri: c.PasswordResetUri(),
|
PasswordResetUri: c.PasswordResetUri(),
|
||||||
@@ -455,6 +458,7 @@ func (c *Config) ClientUser(withSettings bool) ClientConfig {
|
|||||||
UploadNSFW: c.UploadNSFW(),
|
UploadNSFW: c.UploadNSFW(),
|
||||||
Public: c.Public(),
|
Public: c.Public(),
|
||||||
AuthMode: c.AuthMode(),
|
AuthMode: c.AuthMode(),
|
||||||
|
UsersPath: c.UsersPath(),
|
||||||
LoginUri: c.LoginUri(),
|
LoginUri: c.LoginUri(),
|
||||||
RegisterUri: c.RegisterUri(),
|
RegisterUri: c.RegisterUri(),
|
||||||
PasswordLength: c.PasswordLength(),
|
PasswordLength: c.PasswordLength(),
|
||||||
|
|||||||
@@ -173,6 +173,9 @@ func (c *Config) Propagate() {
|
|||||||
// Set minimum password length.
|
// Set minimum password length.
|
||||||
entity.PasswordLength = c.PasswordLength()
|
entity.PasswordLength = c.PasswordLength()
|
||||||
|
|
||||||
|
// Set path for user assets.
|
||||||
|
entity.UsersPath = c.UsersPath()
|
||||||
|
|
||||||
// Set API preview and download default tokens.
|
// Set API preview and download default tokens.
|
||||||
entity.PreviewToken.Set(c.PreviewToken(), entity.TokenConfig)
|
entity.PreviewToken.Set(c.PreviewToken(), entity.TokenConfig)
|
||||||
entity.DownloadToken.Set(c.DownloadToken(), entity.TokenConfig)
|
entity.DownloadToken.Set(c.DownloadToken(), entity.TokenConfig)
|
||||||
|
|||||||
@@ -90,8 +90,8 @@ func (c *Config) CreateDirectories() error {
|
|||||||
|
|
||||||
if c.UsersPath() == "" {
|
if c.UsersPath() == "" {
|
||||||
return notFoundError("users")
|
return notFoundError("users")
|
||||||
} else if err := os.MkdirAll(c.UsersPath(), fs.ModeDir); err != nil {
|
} else if err := os.MkdirAll(c.UsersStoragePath(), fs.ModeDir); err != nil {
|
||||||
return createError(c.UsersPath(), err)
|
return createError(c.UsersStoragePath(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.CmdCachePath() == "" {
|
if c.CmdCachePath() == "" {
|
||||||
@@ -325,19 +325,28 @@ func (c *Config) SidecarWritable() bool {
|
|||||||
return !c.ReadOnly() || c.SidecarPathIsAbs()
|
return !c.ReadOnly() || c.SidecarPathIsAbs()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UsersPath returns the storage base path for user assets like
|
// UsersPath returns the relative base path for user assets.
|
||||||
// avatar images and other media that should not be indexed.
|
|
||||||
func (c *Config) UsersPath() string {
|
func (c *Config) UsersPath() string {
|
||||||
// Set default.
|
// Set default.
|
||||||
if c.options.UsersPath == "" {
|
if c.options.UsersPath == "" {
|
||||||
c.options.UsersPath = filepath.Join(c.StoragePath(), "users")
|
return "users"
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.options.UsersPath
|
return clean.UserPath(c.options.UsersPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserPath returns the storage path for user assets.
|
// UsersStoragePath returns the users storage base path.
|
||||||
func (c *Config) UserPath(userUid string) string {
|
func (c *Config) UsersStoragePath() string {
|
||||||
|
return filepath.Join(c.StoragePath(), c.UsersPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsersOriginalsPath returns the users originals base path.
|
||||||
|
func (c *Config) UsersOriginalsPath() string {
|
||||||
|
return filepath.Join(c.OriginalsPath(), c.UsersPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserStoragePath returns the storage path for user assets.
|
||||||
|
func (c *Config) UserStoragePath(userUid string) string {
|
||||||
if !rnd.IsUID(userUid, 0) {
|
if !rnd.IsUID(userUid, 0) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -357,7 +366,7 @@ func (c *Config) UserUploadPath(userUid, token string) (string, error) {
|
|||||||
return "", fmt.Errorf("invalid uid")
|
return "", fmt.Errorf("invalid uid")
|
||||||
}
|
}
|
||||||
|
|
||||||
dir := filepath.Join(c.UserPath(userUid), "upload", clean.Token(token))
|
dir := filepath.Join(c.UserStoragePath(userUid), "upload", clean.Token(token))
|
||||||
|
|
||||||
if err := os.MkdirAll(dir, fs.ModeDir); err != nil {
|
if err := os.MkdirAll(dir, fs.ModeDir); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ func TestConfig_SidecarPath(t *testing.T) {
|
|||||||
|
|
||||||
func TestConfig_UsersPath(t *testing.T) {
|
func TestConfig_UsersPath(t *testing.T) {
|
||||||
c := NewConfig(CliTestContext())
|
c := NewConfig(CliTestContext())
|
||||||
assert.Contains(t, c.UsersPath(), "testdata/users")
|
assert.Contains(t, c.UsersPath(), "users")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfig_UserPath(t *testing.T) {
|
func TestConfig_UserStoragePath(t *testing.T) {
|
||||||
c := NewConfig(CliTestContext())
|
c := NewConfig(CliTestContext())
|
||||||
assert.Equal(t, "", c.UserPath(""))
|
assert.Equal(t, "", c.UserStoragePath(""))
|
||||||
assert.Equal(t, "", c.UserPath("etaetyget"))
|
assert.Equal(t, "", c.UserStoragePath("etaetyget"))
|
||||||
assert.Contains(t, c.UserPath("urjult03ceelhw6k"), "testdata/users/urjult03ceelhw6k")
|
assert.Contains(t, c.UserStoragePath("urjult03ceelhw6k"), "users/urjult03ceelhw6k")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfig_UserUploadPath(t *testing.T) {
|
func TestConfig_UserUploadPath(t *testing.T) {
|
||||||
@@ -50,12 +50,12 @@ func TestConfig_UserUploadPath(t *testing.T) {
|
|||||||
if dir, err := c.UserUploadPath("urjult03ceelhw6k", ""); err != nil {
|
if dir, err := c.UserUploadPath("urjult03ceelhw6k", ""); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
assert.Contains(t, dir, "testdata/users/urjult03ceelhw6k/upload")
|
assert.Contains(t, dir, "users/urjult03ceelhw6k/upload")
|
||||||
}
|
}
|
||||||
if dir, err := c.UserUploadPath("urjult03ceelhw6k", "foo"); err != nil {
|
if dir, err := c.UserUploadPath("urjult03ceelhw6k", "foo"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
assert.Contains(t, dir, "testdata/users/urjult03ceelhw6k/upload/foo")
|
assert.Contains(t, dir, "users/urjult03ceelhw6k/upload/foo")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,11 +142,6 @@ var Flags = CliFlags{
|
|||||||
Usage: "custom relative or absolute sidecar `PATH` *optional*",
|
Usage: "custom relative or absolute sidecar `PATH` *optional*",
|
||||||
EnvVar: "PHOTOPRISM_SIDECAR_PATH",
|
EnvVar: "PHOTOPRISM_SIDECAR_PATH",
|
||||||
}}, {
|
}}, {
|
||||||
Flag: cli.StringFlag{
|
|
||||||
Name: "users-path",
|
|
||||||
Usage: "custom users storage `PATH` *optional*",
|
|
||||||
EnvVar: "PHOTOPRISM_USERS_PATH",
|
|
||||||
}}, {
|
|
||||||
Flag: cli.StringFlag{
|
Flag: cli.StringFlag{
|
||||||
Name: "backup-path, ba",
|
Name: "backup-path, ba",
|
||||||
Usage: "custom backup `PATH` for index backup files *optional*",
|
Usage: "custom backup `PATH` for index backup files *optional*",
|
||||||
@@ -167,6 +162,12 @@ var Flags = CliFlags{
|
|||||||
Usage: "relative originals `PATH` to which the files should be imported by default *optional*",
|
Usage: "relative originals `PATH` to which the files should be imported by default *optional*",
|
||||||
EnvVar: "PHOTOPRISM_IMPORT_DEST",
|
EnvVar: "PHOTOPRISM_IMPORT_DEST",
|
||||||
}}, {
|
}}, {
|
||||||
|
Flag: cli.StringFlag{
|
||||||
|
Name: "users-path",
|
||||||
|
Usage: "relative `PATH` to create base and upload subdirectories for users",
|
||||||
|
EnvVar: "PHOTOPRISM_USERS_PATH",
|
||||||
|
Value: "users",
|
||||||
|
}}, {
|
||||||
Flag: cli.StringFlag{
|
Flag: cli.StringFlag{
|
||||||
Name: "assets-path, as",
|
Name: "assets-path, as",
|
||||||
Usage: "assets `PATH` containing static resources like icons, models, and translations",
|
Usage: "assets `PATH` containing static resources like icons, models, and translations",
|
||||||
|
|||||||
@@ -57,10 +57,9 @@ func (c *Config) Report() (rows [][]string, cols []string) {
|
|||||||
{"originals-limit", fmt.Sprintf("%d", c.OriginalsLimit())},
|
{"originals-limit", fmt.Sprintf("%d", c.OriginalsLimit())},
|
||||||
{"resolution-limit", fmt.Sprintf("%d", c.ResolutionLimit())},
|
{"resolution-limit", fmt.Sprintf("%d", c.ResolutionLimit())},
|
||||||
|
|
||||||
// Other paths.
|
// Storage.
|
||||||
{"storage-path", c.StoragePath()},
|
{"storage-path", c.StoragePath()},
|
||||||
{"sidecar-path", c.SidecarPath()},
|
{"sidecar-path", c.SidecarPath()},
|
||||||
{"users-path", c.UsersPath()},
|
|
||||||
{"albums-path", c.AlbumsPath()},
|
{"albums-path", c.AlbumsPath()},
|
||||||
{"backup-path", c.BackupPath()},
|
{"backup-path", c.BackupPath()},
|
||||||
{"cache-path", c.CachePath()},
|
{"cache-path", c.CachePath()},
|
||||||
@@ -69,6 +68,9 @@ func (c *Config) Report() (rows [][]string, cols []string) {
|
|||||||
{"thumb-cache-path", c.ThumbCachePath()},
|
{"thumb-cache-path", c.ThumbCachePath()},
|
||||||
{"import-path", c.ImportPath()},
|
{"import-path", c.ImportPath()},
|
||||||
{"import-dest", c.ImportDest()},
|
{"import-dest", c.ImportDest()},
|
||||||
|
{"users-path", c.UsersPath()},
|
||||||
|
{"users-storage-path", c.UsersStoragePath()},
|
||||||
|
{"users-originals-path", c.UsersOriginalsPath()},
|
||||||
{"assets-path", c.AssetsPath()},
|
{"assets-path", c.AssetsPath()},
|
||||||
{"static-path", c.StaticPath()},
|
{"static-path", c.StaticPath()},
|
||||||
{"build-path", c.BuildPath()},
|
{"build-path", c.BuildPath()},
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ var UsernameLength = 1
|
|||||||
// PasswordLength specifies the minimum length of the password in characters.
|
// PasswordLength specifies the minimum length of the password in characters.
|
||||||
var PasswordLength = 4
|
var PasswordLength = 4
|
||||||
|
|
||||||
|
// UsersPath is the relative path for user assets.
|
||||||
|
var UsersPath = "users"
|
||||||
|
|
||||||
// Users represents a list of users.
|
// Users represents a list of users.
|
||||||
type Users []User
|
type Users []User
|
||||||
|
|
||||||
@@ -118,6 +121,8 @@ func FindUser(find User) *User {
|
|||||||
stmt = stmt.Where("id = ?", find.ID)
|
stmt = stmt.Where("id = ?", find.ID)
|
||||||
} else if rnd.IsUID(find.UserUID, UserUID) {
|
} else if rnd.IsUID(find.UserUID, UserUID) {
|
||||||
stmt = stmt.Where("user_uid = ?", find.UserUID)
|
stmt = stmt.Where("user_uid = ?", find.UserUID)
|
||||||
|
} else if find.AuthProvider != "" && find.AuthID != "" && find.UserName != "" {
|
||||||
|
stmt = stmt.Where("auth_provider = ? AND auth_id = ? OR user_name = ?", find.AuthProvider, find.AuthID, find.UserName)
|
||||||
} else if find.UserName != "" {
|
} else if find.UserName != "" {
|
||||||
stmt = stmt.Where("user_name = ?", find.UserName)
|
stmt = stmt.Where("user_name = ?", find.UserName)
|
||||||
} else if find.UserEmail != "" {
|
} else if find.UserEmail != "" {
|
||||||
@@ -432,7 +437,7 @@ func (m *User) DefaultBasePath() string {
|
|||||||
if s := m.Handle(); s == "" {
|
if s := m.Handle(); s == "" {
|
||||||
return ""
|
return ""
|
||||||
} else {
|
} else {
|
||||||
return fmt.Sprintf("users/%s", s)
|
return path.Join(UsersPath, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,11 +15,13 @@ import (
|
|||||||
gc "github.com/patrickmn/go-cache"
|
gc "github.com/patrickmn/go-cache"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/api"
|
"github.com/photoprism/photoprism/internal/api"
|
||||||
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
"github.com/photoprism/photoprism/internal/entity"
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
"github.com/photoprism/photoprism/internal/form"
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Authentication cache with an expiration time of 5 minutes.
|
// Authentication cache with an expiration time of 5 minutes.
|
||||||
@@ -38,7 +42,7 @@ func GetAuthUser(key string) *entity.User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BasicAuth implements an HTTP request handler that adds basic authentication.
|
// BasicAuth implements an HTTP request handler that adds basic authentication.
|
||||||
func BasicAuth() gin.HandlerFunc {
|
func BasicAuth(conf *config.Config) gin.HandlerFunc {
|
||||||
var validate = func(c *gin.Context) (name, password, key string, valid bool) {
|
var validate = func(c *gin.Context) (name, password, key string, valid bool) {
|
||||||
name, password, key = GetCredentials(c)
|
name, password, key = GetCredentials(c)
|
||||||
|
|
||||||
@@ -106,6 +110,11 @@ func BasicAuth() gin.HandlerFunc {
|
|||||||
// Sync disabled for this account.
|
// Sync disabled for this account.
|
||||||
message := "sync disabled"
|
message := "sync disabled"
|
||||||
|
|
||||||
|
event.AuditWarn([]string{clientIp, "webdav login as %s", message}, clean.LogQuote(name))
|
||||||
|
event.LoginError(clientIp, "webdav", name, api.UserAgent(c), message)
|
||||||
|
} else if err = os.MkdirAll(filepath.Join(conf.OriginalsPath(), user.GetUploadPath()), fs.ModeDir); err != nil {
|
||||||
|
message := "failed to create user upload path"
|
||||||
|
|
||||||
event.AuditWarn([]string{clientIp, "webdav login as %s", message}, clean.LogQuote(name))
|
event.AuditWarn([]string{clientIp, "webdav login as %s", message}, clean.LogQuote(name))
|
||||||
event.LoginError(clientIp, "webdav", name, api.UserAgent(c), message)
|
event.LoginError(clientIp, "webdav", name, api.UserAgent(c), message)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ func registerWebDAVRoutes(router *gin.Engine, conf *config.Config) {
|
|||||||
info = ""
|
info = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
WebDAV(conf.OriginalsPath(), router.Group(conf.BaseUri(WebDAVOriginals), BasicAuth()), conf)
|
WebDAV(conf.OriginalsPath(), router.Group(conf.BaseUri(WebDAVOriginals), BasicAuth(conf)), conf)
|
||||||
log.Infof("webdav: shared %s/%s", conf.BaseUri(WebDAVOriginals), info)
|
log.Infof("webdav: shared %s/%s", conf.BaseUri(WebDAVOriginals), info)
|
||||||
|
|
||||||
if conf.ImportPath() != "" {
|
if conf.ImportPath() != "" {
|
||||||
WebDAV(conf.ImportPath(), router.Group(conf.BaseUri(WebDAVImport), BasicAuth()), conf)
|
WebDAV(conf.ImportPath(), router.Group(conf.BaseUri(WebDAVImport), BasicAuth(conf)), conf)
|
||||||
log.Infof("webdav: shared %s/%s", conf.BaseUri(WebDAVImport), info)
|
log.Infof("webdav: shared %s/%s", conf.BaseUri(WebDAVImport), info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user