diff --git a/application/migrator/user.go b/application/migrator/user.go index 273d8f09..1f5c9cd7 100644 --- a/application/migrator/user.go +++ b/application/migrator/user.go @@ -3,6 +3,7 @@ package migrator import ( "context" "fmt" + "github.com/cloudreve/Cloudreve/v4/application/migrator/model" "github.com/cloudreve/Cloudreve/v4/ent/user" "github.com/cloudreve/Cloudreve/v4/inventory/types" @@ -71,7 +72,7 @@ func (m *Migrator) migrateUser() error { SetNick(u.Nick). SetStatus(userStatus). SetStorage(int64(u.Storage)). - SetGroupID(int(u.GroupID)). + AddGroupIDs(int(u.GroupID)). SetSettings(setting). SetPassword(u.Password) diff --git a/ent/user_extension.go b/ent/user_extension.go new file mode 100644 index 00000000..dc6fa3d4 --- /dev/null +++ b/ent/user_extension.go @@ -0,0 +1,69 @@ +package ent + +import ( + "github.com/cloudreve/Cloudreve/v4/inventory/types" + "github.com/samber/lo" +) + +func (u *User) AnyGroup(predict func(*Group) bool) bool { + return !lo.NoneBy(u.Edges.Group, predict) +} + +func (u *User) EnforceGroupPermission(perm types.GroupPermission) bool { + return !lo.NoneBy(u.Edges.Group, func(item *Group) bool { + return item.Permissions.Enabled(int(perm)) + }) +} + +func (u *User) GroupMaxStorage() int64 { + return lo.MaxBy(u.Edges.Group, func(a *Group, b *Group) bool { + return a.MaxStorage > b.MaxStorage + }).MaxStorage +} + +func (u *User) GroupMaxWalkedFiles() int { + return max(lo.MaxBy(u.Edges.Group, func(a *Group, b *Group) bool { + return a.Settings.MaxWalkedFiles > b.Settings.MaxWalkedFiles + }).Settings.MaxWalkedFiles, 1) +} + +func (u *User) GroupMaxTrashRetention() int { + return lo.MaxBy(u.Edges.Group, func(a *Group, b *Group) bool { + return a.Settings.TrashRetention > b.Settings.TrashRetention + }).Settings.TrashRetention +} + +func (u *User) GroupMaxDecompressSize() int64 { + return lo.MaxBy(u.Edges.Group, func(a *Group, b *Group) bool { + return a.Settings.DecompressSize > b.Settings.DecompressSize + }).Settings.DecompressSize +} + +func (u *User) GroupMaxCompressSize() int64 { + return lo.MaxBy(u.Edges.Group, func(a *Group, b *Group) bool { + return a.Settings.CompressSize > b.Settings.CompressSize + }).Settings.CompressSize +} + +func (u *User) GroupMaxSpeedLimit() int { + return lo.MaxBy(u.Edges.Group, func(a *Group, b *Group) bool { + return a.SpeedLimit > b.SpeedLimit + }).SpeedLimit +} + +func (u *User) GroupMaxSourceBatchSize() int { + return lo.MaxBy(u.Edges.Group, func(a *Group, b *Group) bool { + return a.Settings.SourceBatchSize > b.Settings.SourceBatchSize + }).Settings.SourceBatchSize +} + +func (u *User) GroupMaxAria2BatchSize() int { + return lo.MaxBy(u.Edges.Group, func(a *Group, b *Group) bool { + return a.Settings.Aria2BatchSize > b.Settings.Aria2BatchSize + }).Settings.Aria2BatchSize +} + +func (u *User) GetPrimaryGroup() *Group { + // TODO: design user primary group + panic("implement me") +} diff --git a/go.mod b/go.mod index e975e6f6..8b38c7a3 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( github.com/qiniu/go-sdk/v7 v7.19.0 github.com/rafaeljusto/redigomock v0.0.0-20191117212112-00b2509252a1 github.com/robfig/cron/v3 v3.0.1 - github.com/samber/lo v1.38.1 + github.com/samber/lo v1.51.0 github.com/speps/go-hashids v2.0.0+incompatible github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index ed10902b..f87e04b1 100644 --- a/go.sum +++ b/go.sum @@ -864,8 +864,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= -github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI= +github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= diff --git a/inventory/user.go b/inventory/user.go index f394050a..546000b1 100644 --- a/inventory/user.go +++ b/inventory/user.go @@ -16,6 +16,7 @@ import ( "github.com/cloudreve/Cloudreve/v4/ent" "github.com/cloudreve/Cloudreve/v4/ent/davaccount" "github.com/cloudreve/Cloudreve/v4/ent/file" + "github.com/cloudreve/Cloudreve/v4/ent/group" "github.com/cloudreve/Cloudreve/v4/ent/passkey" "github.com/cloudreve/Cloudreve/v4/ent/schema" "github.com/cloudreve/Cloudreve/v4/ent/task" @@ -309,7 +310,7 @@ func (c *userClient) Create(ctx context.Context, args *NewUserArgs) (*ent.User, SetEmail(args.Email). SetNick(nick). SetStatus(args.Status). - SetGroupID(args.GroupID). + AddGroupIDs(args.GroupID). SetAvatar(args.Avatar) if args.PlainPassword != "" { @@ -334,7 +335,7 @@ func (c *userClient) Create(ctx context.Context, args *NewUserArgs) (*ent.User, if newUser.ID == 1 { // For the first user registered, elevate it to admin group. - if _, err := newUser.Update().SetGroupID(1).Save(ctx); err != nil { + if _, err := newUser.Update().AddGroupIDs(1).Save(ctx); err != nil { return newUser, fmt.Errorf("failed to elevate user to admin: %w", err) } } @@ -434,14 +435,14 @@ func (c *userClient) AnonymousUser(ctx context.Context) (*ent.User, error) { anonymous := &ent.User{ Settings: &types.UserSetting{}, } - anonymous.SetGroup(anonymousGroup) + anonymous.SetGroup([]*ent.Group{anonymousGroup}) return anonymous, nil } func (c *userClient) ListUsers(ctx context.Context, args *ListUserParameters) (*ListUserResult, error) { query := c.client.User.Query() if args.GroupID != 0 { - query = query.Where(user.GroupUsers(args.GroupID)) + query = query.Where(user.HasGroupWith(group.ID(args.GroupID))) } if args.Status != "" { query = query.Where(user.StatusEQ(args.Status)) @@ -482,7 +483,7 @@ func (c *userClient) Upsert(ctx context.Context, u *ent.User, password, twoFa st SetNick(u.Nick). SetAvatar(u.Avatar). SetStatus(u.Status). - SetGroupID(u.GroupUsers). + AddGroup(u.Edges.Group...). SetPassword(u.Password). SetSettings(&types.UserSetting{}) @@ -502,7 +503,7 @@ func (c *userClient) Upsert(ctx context.Context, u *ent.User, password, twoFa st SetNick(u.Nick). SetAvatar(u.Avatar). SetStatus(u.Status). - SetGroupID(u.GroupUsers) + AddGroup(u.Edges.Group...) if password != "" { pwdDigest, err := digestPassword(password) diff --git a/middleware/auth.go b/middleware/auth.go index 4df7ed7f..58dde09f 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -144,15 +144,7 @@ func WebDAVAuth() gin.HandlerFunc { } // 用户组已启用WebDAV? - group, err := expectedUser.Edges.GroupOrErr() - if err != nil { - l.Debug("WebDAVAuth: user group not found: %s", err) - c.Status(http.StatusInternalServerError) - c.Abort() - return - } - - if !group.Permissions.Enabled(int(types.GroupPermissionWebDAV)) { + if !expectedUser.EnforceGroupPermission(types.GroupPermissionWebDAV) { c.Status(http.StatusForbidden) l.Debug("WebDAVAuth: user %q does not have WebDAV permission.", expectedUser.Email) c.Abort() @@ -262,7 +254,7 @@ func OSSCallbackAuth() gin.HandlerFunc { func IsAdmin() gin.HandlerFunc { return func(c *gin.Context) { user := inventory.UserFromContext(c) - if !user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) { + if !user.EnforceGroupPermission(types.GroupPermissionIsAdmin) { c.JSON(200, serializer.ErrWithDetails(c, serializer.CodeNoPermissionErr, "", nil)) c.Abort() return diff --git a/pkg/filemanager/fs/dbfs/dbfs.go b/pkg/filemanager/fs/dbfs/dbfs.go index 7d57c7d6..425a9e6e 100644 --- a/pkg/filemanager/fs/dbfs/dbfs.go +++ b/pkg/filemanager/fs/dbfs/dbfs.go @@ -229,13 +229,8 @@ func (f *DBFS) Capacity(ctx context.Context, u *ent.User) (*fs.Capacity, error) res = &fs.Capacity{} ) - requesterGroup, err := u.Edges.GroupOrErr() - if err != nil { - return nil, serializer.NewError(serializer.CodeDBError, "Failed to get user's group", err) - } - res.Used = f.user.Storage - res.Total = requesterGroup.MaxStorage + res.Total = f.user.GroupMaxStorage() return res, nil } @@ -426,7 +421,7 @@ func (f *DBFS) Get(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.Fil } target.FileExtendedInfo = extendedInfo - if target.OwnerID() == f.user.ID || f.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) { + if target.OwnerID() == f.user.ID || f.user.EnforceGroupPermission(types.GroupPermissionIsAdmin) { target.FileExtendedInfo.Shares = target.Model.Edges.Shares if target.Model.Props != nil { target.FileExtendedInfo.View = target.Model.Props.View @@ -463,7 +458,7 @@ func (f *DBFS) Get(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.Fil if f.user.Edges.Group == nil { return nil, fmt.Errorf("user group not loaded") } - limit := max(f.user.Edges.Group.Settings.MaxWalkedFiles, 1) + limit := f.user.GroupMaxWalkedFiles() // disable load metadata to speed up ctxWalk := context.WithValue(ctx, inventory.LoadFilePublicMetadata{}, false) @@ -552,7 +547,7 @@ func (f *DBFS) Walk(ctx context.Context, path *fs.URI, depth int, walk fs.WalkFu if f.user.Edges.Group == nil { return fmt.Errorf("user group not loaded") } - limit := max(f.user.Edges.Group.Settings.MaxWalkedFiles, 1) + limit := f.user.GroupMaxWalkedFiles() if err := navigator.Walk(ctx, []*File{target}, limit, depth, func(files []*File, l int) error { for _, file := range files { @@ -647,7 +642,7 @@ func (f *DBFS) createFile(ctx context.Context, parent *File, name string, fileTy // getPreferredPolicy tries to get the preferred storage policy for the given file. func (f *DBFS) getPreferredPolicy(ctx context.Context, file *File) (*ent.StoragePolicy, error) { - ownerGroup := file.Owner().Edges.Group + ownerGroup := file.Owner().GetPrimaryGroup() if ownerGroup == nil { return nil, fmt.Errorf("owner group not loaded") } diff --git a/pkg/filemanager/fs/dbfs/manage.go b/pkg/filemanager/fs/dbfs/manage.go index f0471df5..16eb5d9e 100644 --- a/pkg/filemanager/fs/dbfs/manage.go +++ b/pkg/filemanager/fs/dbfs/manage.go @@ -290,7 +290,7 @@ func (f *DBFS) SoftDelete(ctx context.Context, path ...*fs.URI) error { if err := fc.UpsertMetadata(ctx, target.Model, map[string]string{ MetadataRestoreUri: target.Uri(true).String(), MetadataExpectedCollectTime: strconv.FormatInt( - time.Now().Add(time.Duration(target.Owner().Edges.Group.Settings.TrashRetention)*time.Second).Unix(), + time.Now().Add(time.Duration(target.Owner().GroupMaxTrashRetention())*time.Second).Unix(), 10), }, nil); err != nil { _ = inventory.Rollback(tx) @@ -665,7 +665,7 @@ func (f *DBFS) TraverseFile(ctx context.Context, fileID int) (fs.File, error) { return nil, err } - if fileModel.OwnerID != f.user.ID && !f.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) { + if fileModel.OwnerID != f.user.ID && !f.user.EnforceGroupPermission(types.GroupPermissionIsAdmin) { return nil, fs.ErrOwnerOnly.WithError(fmt.Errorf("only file owner can traverse file's uri")) } @@ -800,7 +800,7 @@ func (f *DBFS) copyFiles(ctx context.Context, targets map[Navigator][]*File, des if f.user.Edges.Group == nil { return nil, nil, fmt.Errorf("user group not loaded") } - limit := max(f.user.Edges.Group.Settings.MaxWalkedFiles, 1) + limit := f.user.GroupMaxWalkedFiles() capacity, err := f.Capacity(ctx, destination.Owner()) if err != nil { return nil, nil, fmt.Errorf("copy files: failed to destination owner capacity: %w", err) diff --git a/pkg/filemanager/fs/dbfs/my_navigator.go b/pkg/filemanager/fs/dbfs/my_navigator.go index b607ba21..4d51dc5f 100644 --- a/pkg/filemanager/fs/dbfs/my_navigator.go +++ b/pkg/filemanager/fs/dbfs/my_navigator.go @@ -3,6 +3,7 @@ package dbfs import ( "context" "fmt" + "github.com/cloudreve/Cloudreve/v4/inventory/types" "github.com/cloudreve/Cloudreve/v4/ent" @@ -92,7 +93,7 @@ func (n *myNavigator) To(ctx context.Context, path *fs.URI) (*File, error) { return nil, fs.ErrPathNotExist.WithError(fmt.Errorf("user not found: %w", err)) } - if targetUser.Status != user.StatusActive && !n.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) { + if targetUser.Status != user.StatusActive && !n.user.EnforceGroupPermission(types.GroupPermissionIsAdmin) { return nil, fs.ErrPathNotExist.WithError(fmt.Errorf("inactive user")) } diff --git a/pkg/filemanager/fs/dbfs/props.go b/pkg/filemanager/fs/dbfs/props.go index 04b39646..2669dd3b 100644 --- a/pkg/filemanager/fs/dbfs/props.go +++ b/pkg/filemanager/fs/dbfs/props.go @@ -23,7 +23,7 @@ func (f *DBFS) PatchProps(ctx context.Context, uri *fs.URI, props *types.FilePro return fmt.Errorf("failed to get target file: %w", err) } - if target.OwnerID() != f.user.ID && !f.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) { + if target.OwnerID() != f.user.ID && !f.user.EnforceGroupPermission(types.GroupPermissionIsAdmin) { return fs.ErrOwnerOnly.WithError(fmt.Errorf("only file owner can modify file props")) } diff --git a/pkg/filemanager/fs/dbfs/share_navigator.go b/pkg/filemanager/fs/dbfs/share_navigator.go index c03caf83..2e10d688 100644 --- a/pkg/filemanager/fs/dbfs/share_navigator.go +++ b/pkg/filemanager/fs/dbfs/share_navigator.go @@ -156,7 +156,7 @@ func (n *shareNavigator) Root(ctx context.Context, path *fs.URI) (*File, error) return nil, ErrShareNotFound } - if n.user.ID != n.owner.ID && !n.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionShareDownload)) { + if n.user.ID != n.owner.ID && !n.user.EnforceGroupPermission(types.GroupPermissionShareDownload) { if inventory.IsAnonymousUser(n.user) { return nil, serializer.NewError( serializer.CodeAnonymouseAccessDenied, diff --git a/pkg/filemanager/manager/archive.go b/pkg/filemanager/manager/archive.go index daded9cf..e0cc4d06 100644 --- a/pkg/filemanager/manager/archive.go +++ b/pkg/filemanager/manager/archive.go @@ -95,8 +95,8 @@ func (m *manager) ListArchiveFiles(ctx context.Context, uri *fs.URI, entity, zip } // Validate file size - if m.user.Edges.Group.Settings.DecompressSize > 0 && file.Size() > m.user.Edges.Group.Settings.DecompressSize { - return nil, fs.ErrFileSizeTooBig.WithError(fmt.Errorf("file size %d exceeds the limit %d", file.Size(), m.user.Edges.Group.Settings.DecompressSize)) + if m.user.GroupMaxDecompressSize() > 0 && file.Size() > m.user.GroupMaxDecompressSize() { + return nil, fs.ErrFileSizeTooBig.WithError(fmt.Errorf("file size %d exceeds the limit %d", file.Size(), m.user.GroupMaxDecompressSize())) } found, targetEntity := fs.FindDesiredEntity(file, entity, m.hasher, nil) diff --git a/pkg/filemanager/manager/entity.go b/pkg/filemanager/manager/entity.go index bf88ef70..c1940a56 100644 --- a/pkg/filemanager/manager/entity.go +++ b/pkg/filemanager/manager/entity.go @@ -61,7 +61,7 @@ type ( func (m *manager) GetDirectLink(ctx context.Context, urls ...*fs.URI) ([]DirectLink, error) { ae := serializer.NewAggregateError() res := make([]DirectLink, 0, len(urls)) - useRedirect := m.user.Edges.Group.Settings.RedirectedSource + useRedirect := m.user.GetPrimaryGroup().Settings.RedirectedSource fileClient := m.dep.FileClient() siteUrl := m.settings.SiteURL(ctx) @@ -76,7 +76,7 @@ func (m *manager) GetDirectLink(ctx context.Context, urls ...*fs.URI) ([]DirectL continue } - if file.OwnerID() != m.user.ID && !m.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) { + if file.OwnerID() != m.user.ID && !m.user.EnforceGroupPermission(types.GroupPermissionIsAdmin) { ae.Add(url.String(), fs.ErrOwnerOnly) continue } @@ -98,9 +98,9 @@ func (m *manager) GetDirectLink(ctx context.Context, urls ...*fs.URI) ([]DirectL } if useRedirect { - reuseExisting := !m.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionUniqueRedirectDirectLink)) + reuseExisting := !m.user.EnforceGroupPermission(types.GroupPermissionUniqueRedirectDirectLink) // Use redirect source - link, err := fileClient.CreateDirectLink(ctx, file.ID(), file.Name(), m.user.Edges.Group.SpeedLimit, reuseExisting) + link, err := fileClient.CreateDirectLink(ctx, file.ID(), file.Name(), m.user.GroupMaxSpeedLimit(), reuseExisting) if err != nil { ae.Add(url.String(), err) continue @@ -122,7 +122,7 @@ func (m *manager) GetDirectLink(ctx context.Context, urls ...*fs.URI) ([]DirectL source := entitysource.NewEntitySource(target, d, policy, m.auth, m.settings, m.hasher, m.dep.RequestClient(), m.l, m.config, m.dep.MimeDetector(ctx)) sourceUrl, err := source.Url(ctx, - entitysource.WithSpeedLimit(int64(m.user.Edges.Group.SpeedLimit)), + entitysource.WithSpeedLimit(int64(m.user.GroupMaxSpeedLimit())), entitysource.WithDisplayName(file.Name()), ) if err != nil { diff --git a/pkg/filemanager/workflows/archive.go b/pkg/filemanager/workflows/archive.go index bcef9bb2..49cb55d6 100644 --- a/pkg/filemanager/workflows/archive.go +++ b/pkg/filemanager/workflows/archive.go @@ -233,7 +233,7 @@ func (m *CreateArchiveTask) listEntitiesAndSendToSlave(ctx context.Context, dep } } }), - fs.WithMaxArchiveSize(user.Edges.Group.Settings.CompressSize), + fs.WithMaxArchiveSize(user.GroupMaxCompressSize()), ) if err != nil { return task.StatusError, fmt.Errorf("failed to compress files: %w", err) @@ -390,7 +390,7 @@ func (m *CreateArchiveTask) createArchiveFile(ctx context.Context, dep dependenc m.Unlock() failed, err := fm.CreateArchive(ctx, uris, zipFile, fs.WithArchiveCompression(true), - fs.WithMaxArchiveSize(user.Edges.Group.Settings.CompressSize), + fs.WithMaxArchiveSize(user.GroupMaxCompressSize()), fs.WithProgressFunc(func(current, diff int64, total int64) { atomic.AddInt64(&m.progress[ProgressTypeArchiveSize].Current, diff) atomic.AddInt64(&m.progress[ProgressTypeArchiveCount].Current, 1) diff --git a/pkg/filemanager/workflows/extract.go b/pkg/filemanager/workflows/extract.go index 181bd034..712763bf 100644 --- a/pkg/filemanager/workflows/extract.go +++ b/pkg/filemanager/workflows/extract.go @@ -182,9 +182,9 @@ func (m *ExtractArchiveTask) createSlaveExtractTask(ctx context.Context, dep dep } // Validate file size - if user.Edges.Group.Settings.DecompressSize > 0 && archiveFile.Size() > user.Edges.Group.Settings.DecompressSize { + if user.GroupMaxDecompressSize() > 0 && archiveFile.Size() > user.GroupMaxDecompressSize() { return task.StatusError, - fmt.Errorf("file size %d exceeds the limit %d (%w)", archiveFile.Size(), user.Edges.Group.Settings.DecompressSize, queue.CriticalErr) + fmt.Errorf("file size %d exceeds the limit %d (%w)", archiveFile.Size(), user.GroupMaxDecompressSize(), queue.CriticalErr) } // Create slave task @@ -269,9 +269,9 @@ func (m *ExtractArchiveTask) masterExtractArchive(ctx context.Context, dep depen } // Validate file size - if user.Edges.Group.Settings.DecompressSize > 0 && archiveFile.Size() > user.Edges.Group.Settings.DecompressSize { + if user.GroupMaxDecompressSize() > 0 && archiveFile.Size() > user.GroupMaxDecompressSize() { return task.StatusError, - fmt.Errorf("file size %d exceeds the limit %d (%w)", archiveFile.Size(), user.Edges.Group.Settings.DecompressSize, queue.CriticalErr) + fmt.Errorf("file size %d exceeds the limit %d (%w)", archiveFile.Size(), user.GroupMaxDecompressSize(), queue.CriticalErr) } es, err := fm.GetEntitySource(ctx, 0, fs.WithEntity(archiveFile.PrimaryEntity())) diff --git a/pkg/filemanager/workflows/remote_download.go b/pkg/filemanager/workflows/remote_download.go index 4425c4d9..b2a0a461 100644 --- a/pkg/filemanager/workflows/remote_download.go +++ b/pkg/filemanager/workflows/remote_download.go @@ -199,7 +199,7 @@ func (m *RemoteDownloadTask) createDownloadTask(ctx context.Context, dep depende } // Create download task - handle, err := m.d.CreateTask(ctx, torrentUrl, user.Edges.Group.Settings.RemoteDownloadOptions) + handle, err := m.d.CreateTask(ctx, torrentUrl, user.GetPrimaryGroup().Settings.RemoteDownloadOptions) if err != nil { return task.StatusError, fmt.Errorf("failed to create download task: %w", err) } diff --git a/pkg/webdav/webdav.go b/pkg/webdav/webdav.go index fd630736..5f2f0557 100644 --- a/pkg/webdav/webdav.go +++ b/pkg/webdav/webdav.go @@ -347,10 +347,10 @@ func handleGetHeadPost(c *gin.Context, user *ent.User, fm manager.FileManager) ( defer es.Close() - es.Apply(entitysource.WithSpeedLimit(int64(user.Edges.Group.SpeedLimit))) + es.Apply(entitysource.WithSpeedLimit(int64(user.GroupMaxSpeedLimit()))) if es.ShouldInternalProxy() || (user.Edges.DavAccounts[0].Options.Enabled(int(types.DavAccountProxy)) && - user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionWebDAVProxy))) { + user.EnforceGroupPermission(types.GroupPermissionWebDAVProxy)) { es.Serve(c.Writer, c.Request) } else { settings := dependency.FromContext(c).SettingProvider() diff --git a/service/admin/user.go b/service/admin/user.go index 8ae2aea9..05f119bd 100644 --- a/service/admin/user.go +++ b/service/admin/user.go @@ -2,7 +2,9 @@ package admin import ( "context" + "slices" "strconv" + "unicode/utf8" "github.com/cloudreve/Cloudreve/v4/application/dependency" "github.com/cloudreve/Cloudreve/v4/ent" @@ -154,8 +156,10 @@ func (s *UpsertUserService) Update(c *gin.Context) (*GetUserResponse, error) { return nil, serializer.NewError(serializer.CodeDBError, "Failed to get user", err) } - if s.User.ID == 1 && existing.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) { - if s.User.GroupUsers != existing.GroupUsers { + if s.User.ID == 1 && existing.EnforceGroupPermission(types.GroupPermissionIsAdmin) { + if !slices.EqualFunc(s.User.Edges.Group, existing.Edges.Group, func(a *ent.Group, b *ent.Group) bool { + return a.ID == b.ID + }) { return nil, serializer.NewError(serializer.CodeInvalidActionOnDefaultUser, "Cannot change default user's group", nil) } @@ -165,7 +169,7 @@ func (s *UpsertUserService) Update(c *gin.Context) (*GetUserResponse, error) { } - if s.Password != "" && len(s.Password) > 128 { + if s.Password != "" && utf8.RuneCountInString(s.Password) > 128 { return nil, serializer.NewError(serializer.CodeParamErr, "Password too long", nil) } diff --git a/service/explorer/file.go b/service/explorer/file.go index 05120b84..f32d0024 100644 --- a/service/explorer/file.go +++ b/service/explorer/file.go @@ -100,11 +100,11 @@ func (s *GetDirectLinkService) Get(c *gin.Context) ([]DirectLinkResponse, error) dep := dependency.FromContext(c) u := inventory.UserFromContext(c) - if u.Edges.Group.Settings.SourceBatchSize == 0 { + if u.GroupMaxSourceBatchSize() == 0 { return nil, serializer.NewError(serializer.CodeGroupNotAllowed, "", nil) } - if len(s.Uris) > u.Edges.Group.Settings.SourceBatchSize { + if len(s.Uris) > u.GroupMaxSourceBatchSize() { return nil, serializer.NewError(serializer.CodeBatchSourceSize, "", nil) } @@ -394,7 +394,7 @@ func (s *FileURLService) GetArchiveDownloadSession(c *gin.Context) (*FileURLResp return nil, serializer.NewError(serializer.CodeParamErr, "unknown uri", err) } - if !user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionArchiveDownload)) { + if !user.EnforceGroupPermission(types.GroupPermissionArchiveDownload) { return nil, serializer.NewError(serializer.CodeGroupNotAllowed, "", nil) } @@ -454,7 +454,7 @@ func (s *FileURLService) Get(c *gin.Context) (*FileURLResponse, error) { } res, earliestExpire, err := m.GetEntityUrls(ctx, urlReq, - fs.WithDownloadSpeed(int64(user.Edges.Group.SpeedLimit)), + fs.WithDownloadSpeed(int64(user.GroupMaxSpeedLimit())), fs.WithIsDownload(s.Download), fs.WithNoCache(s.NoCache), fs.WithUrlExpire(&expire), @@ -547,7 +547,7 @@ func (s *DeleteFileService) Delete(c *gin.Context) error { return serializer.NewError(serializer.CodeParamErr, "unknown uri", err) } - if s.UnlinkOnly && !user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionAdvanceDelete)) { + if s.UnlinkOnly && !user.EnforceGroupPermission(types.GroupPermissionAdvanceDelete) { return serializer.NewError(serializer.CodeNoPermissionErr, "advance delete permission is required", nil) } @@ -731,7 +731,7 @@ func (s *ArchiveListFilesService) List(c *gin.Context) (*ArchiveListFilesRespons user := inventory.UserFromContext(c) m := manager.NewFileManager(dep, user) defer m.Recycle() - if !user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionArchiveTask)) { + if !user.EnforceGroupPermission(types.GroupPermissionArchiveTask) { return nil, serializer.NewError(serializer.CodeGroupNotAllowed, "Group not allowed to extract archive files", nil) } diff --git a/service/explorer/workflows.go b/service/explorer/workflows.go index ad7cf098..d9169c7e 100644 --- a/service/explorer/workflows.go +++ b/service/explorer/workflows.go @@ -2,9 +2,10 @@ package explorer import ( "encoding/gob" - "github.com/cloudreve/Cloudreve/v4/pkg/hashid" "time" + "github.com/cloudreve/Cloudreve/v4/pkg/hashid" + "github.com/cloudreve/Cloudreve/v4/application/dependency" "github.com/cloudreve/Cloudreve/v4/ent" "github.com/cloudreve/Cloudreve/v4/ent/task" @@ -85,7 +86,7 @@ func (service *DownloadWorkflowService) CreateDownloadTask(c *gin.Context) ([]*T m := manager.NewFileManager(dep, user) defer m.Recycle() - if !user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionRemoteDownload)) { + if !user.EnforceGroupPermission(types.GroupPermissionRemoteDownload) { return nil, serializer.NewError(serializer.CodeGroupNotAllowed, "Group not allowed to download files", nil) } @@ -111,7 +112,7 @@ func (service *DownloadWorkflowService) CreateDownloadTask(c *gin.Context) ([]*T } // 检查批量任务数量 - limit := user.Edges.Group.Settings.Aria2BatchSize + limit := user.GroupMaxAria2BatchSize() if limit > 0 && len(service.Src) > limit { return nil, serializer.NewError(serializer.CodeBatchAria2Size, "", nil) } @@ -186,7 +187,7 @@ func (service *ArchiveWorkflowService) CreateExtractTask(c *gin.Context) (*TaskR m := manager.NewFileManager(dep, user) defer m.Recycle() - if !user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionArchiveTask)) { + if !user.EnforceGroupPermission(types.GroupPermissionArchiveTask) { return nil, serializer.NewError(serializer.CodeGroupNotAllowed, "Group not allowed to compress files", nil) } @@ -225,7 +226,7 @@ func (service *ArchiveWorkflowService) CreateCompressTask(c *gin.Context) (*Task m := manager.NewFileManager(dep, user) defer m.Recycle() - if !user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionArchiveTask)) { + if !user.EnforceGroupPermission(types.GroupPermissionArchiveTask) { return nil, serializer.NewError(serializer.CodeGroupNotAllowed, "Group not allowed to compress files", nil) } @@ -280,7 +281,7 @@ func (service *ImportWorkflowService) CreateImportTask(c *gin.Context) (*TaskRes m := manager.NewFileManager(dep, user) defer m.Recycle() - if !user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) { + if !user.EnforceGroupPermission(types.GroupPermissionIsAdmin) { return nil, serializer.NewError(serializer.CodeGroupNotAllowed, "Only admin can import files", nil) } @@ -388,7 +389,7 @@ func TaskPhaseProgress(c *gin.Context, taskID int) (queue.Progresses, error) { u := inventory.UserFromContext(c) r := dep.TaskRegistry() t, found := r.Get(taskID) - if !found || (t.Owner().ID != u.ID && !u.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin))) { + if !found || (t.Owner().ID != u.ID && !u.EnforceGroupPermission(types.GroupPermissionIsAdmin)) { return queue.Progresses{}, nil } diff --git a/service/setting/webdav.go b/service/setting/webdav.go index 3ed88001..f7e05c61 100644 --- a/service/setting/webdav.go +++ b/service/setting/webdav.go @@ -154,7 +154,7 @@ func (service *CreateDavAccountService) Update(c *gin.Context) (*DavAccount, err } func (service *CreateDavAccountService) validateAndGetBs(user *ent.User) (*boolset.BooleanSet, error) { - if !user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionWebDAV)) { + if !user.EnforceGroupPermission(types.GroupPermissionWebDAV) { return nil, serializer.NewError(serializer.CodeGroupNotAllowed, "WebDAV is not enabled for this user group", nil) } @@ -178,7 +178,7 @@ func (service *CreateDavAccountService) validateAndGetBs(user *ent.User) (*bools boolset.Set(types.DavAccountDisableSysFiles, true, &bs) } - if service.Proxy && user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionWebDAVProxy)) { + if service.Proxy && user.EnforceGroupPermission(types.GroupPermissionWebDAVProxy) { boolset.Set(types.DavAccountProxy, true, &bs) } return &bs, nil diff --git a/service/share/manage.go b/service/share/manage.go index b8ad4d1d..33944f7f 100644 --- a/service/share/manage.go +++ b/service/share/manage.go @@ -37,7 +37,7 @@ func (service *ShareCreateService) Upsert(c *gin.Context, existed int) (string, defer m.Recycle() // Check group permission for creating share link - if !user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionShare)) { + if !user.EnforceGroupPermission(types.GroupPermissionShare) { return "", serializer.NewError(serializer.CodeGroupNotAllowed, "Group permission denied", nil) } @@ -79,7 +79,7 @@ func DeleteShare(c *gin.Context, shareId int) error { share *ent.Share err error ) - if user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) { + if user.EnforceGroupPermission(types.GroupPermissionIsAdmin) { share, err = shareClient.GetByID(ctx, shareId) } else { share, err = shareClient.GetByIDUser(ctx, shareId, user.ID) diff --git a/service/user/response.go b/service/user/response.go index 6c30a2c0..501f145f 100644 --- a/service/user/response.go +++ b/service/user/response.go @@ -107,7 +107,7 @@ type User struct { CreatedAt time.Time `json:"created_at"` PreferredTheme string `json:"preferred_theme,omitempty"` Anonymous bool `json:"anonymous,omitempty"` - Group *Group `json:"group,omitempty"` + Groups []*Group `json:"groups,omitempty"` Pined []types.PinedFile `json:"pined,omitempty"` Language string `json:"language,omitempty"` DisableViewSync bool `json:"disable_view_sync,omitempty"` @@ -156,15 +156,17 @@ func BuildWebAuthnList(credentials []webauthn.Credential) []WebAuthnCredentials // BuildUser 序列化用户 func BuildUser(user *ent.User, idEncoder hashid.Encoder) User { return User{ - ID: hashid.EncodeUserID(idEncoder, user.ID), - Email: user.Email, - Nickname: user.Nick, - Status: user.Status, - Avatar: user.Avatar, - CreatedAt: user.CreatedAt, - PreferredTheme: user.Settings.PreferredTheme, - Anonymous: user.ID == 0, - Group: BuildGroup(user.Edges.Group, idEncoder), + ID: hashid.EncodeUserID(idEncoder, user.ID), + Email: user.Email, + Nickname: user.Nick, + Status: user.Status, + Avatar: user.Avatar, + CreatedAt: user.CreatedAt, + PreferredTheme: user.Settings.PreferredTheme, + Anonymous: user.ID == 0, + Groups: lo.Map(user.Edges.Group, func(item *ent.Group, index int) *Group { + return BuildGroup(item, idEncoder) + }), Pined: user.Settings.Pined, Language: user.Settings.Language, DisableViewSync: user.Settings.DisableViewSync, @@ -202,10 +204,9 @@ func BuildUserRedacted(u *ent.User, level int, idEncoder hashid.Encoder) User { Avatar: userRaw.Avatar, CreatedAt: userRaw.CreatedAt, ShareLinksInProfile: userRaw.ShareLinksInProfile, - } - - if userRaw.Group != nil { - user.Group = RedactedGroup(userRaw.Group) + Groups: lo.Map(userRaw.Groups, func(item *Group, level int) *Group { + return RedactedGroup(item) + }), } if level == RedactLevelUser {