diff --git a/assets b/assets index 0b722766..807360a8 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 0b722766f8808b0a0aaa3c9f3e4766972d746d22 +Subproject commit 807360a8a6646a1c4bf8f1151e75da17a3eb457c diff --git a/inventory/setting.go b/inventory/setting.go index 3e2aef1c..78ce9de9 100644 --- a/inventory/setting.go +++ b/inventory/setting.go @@ -685,6 +685,7 @@ var DefaultSettings = map[string]string{ "fts_tika_max_file_size": "26214400", "fts_chunk_size": "2000", "viewer_default_apps": "{}", + "expose_user_email": "1", } var RedactedSettings = map[string]struct{}{ diff --git a/pkg/setting/provider.go b/pkg/setting/provider.go index a95eaa24..e9816c45 100644 --- a/pkg/setting/provider.go +++ b/pkg/setting/provider.go @@ -236,6 +236,9 @@ type ( FTSChunkSize(ctx context.Context) int // DefaultViewerMapping returns the default viewer mapping. DefaultViewerMapping(ctx context.Context) types.DefaultViewerMapping + // ExposeUserEmail returns true if user email should be exposed to other + // signed-in users in redacted responses. + ExposeUserEmail(ctx context.Context) bool } UseFirstSiteUrlCtxKey = struct{} ) @@ -860,6 +863,10 @@ func (s *settingProvider) RegisterEnabled(ctx context.Context) bool { return s.getBoolean(ctx, "register_enabled", false) } +func (s *settingProvider) ExposeUserEmail(ctx context.Context) bool { + return s.getBoolean(ctx, "expose_user_email", true) +} + func (s *settingProvider) SiteBasic(ctx context.Context) *SiteBasic { return &SiteBasic{ Name: s.getString(ctx, "siteName", ""), diff --git a/routers/controllers/user.go b/routers/controllers/user.go index d9a6ef28..65e08ed1 100644 --- a/routers/controllers/user.go +++ b/routers/controllers/user.go @@ -208,7 +208,7 @@ func UserGet(c *gin.Context) { redactLevel = user.RedactLevelAnonymous } c.JSON(200, serializer.Response{ - Data: user.BuildUserRedacted(u, redactLevel, dependency.FromContext(c).HashIDEncoder()), + Data: user.BuildUserRedacted(c, u, redactLevel, dependency.FromContext(c).HashIDEncoder()), }) } @@ -368,7 +368,7 @@ func UserSearch(c *gin.Context) { hasher := dependency.FromContext(c).HashIDEncoder() c.JSON(200, serializer.Response{ Data: lo.Map(u, func(item *ent.User, index int) user.User { - return user.BuildUserRedacted(item, user.RedactLevelUser, hasher) + return user.BuildUserRedacted(c, item, user.RedactLevelUser, hasher) }), }) } diff --git a/service/explorer/response.go b/service/explorer/response.go index 0afa97ea..f4084f54 100644 --- a/service/explorer/response.go +++ b/service/explorer/response.go @@ -343,7 +343,7 @@ type Share struct { SourceUri string `json:"source_uri,omitempty"` } -func BuildShare(s *ent.Share, base *url.URL, hasher hashid.Encoder, requester *ent.User, owner *ent.User, +func BuildShare(ctx context.Context, s *ent.Share, base *url.URL, hasher hashid.Encoder, requester *ent.User, owner *ent.User, name string, t types.FileType, unlocked bool, expired bool) *Share { redactLevel := user.RedactLevelAnonymous if !inventory.IsAnonymousUser(requester) { @@ -353,7 +353,7 @@ func BuildShare(s *ent.Share, base *url.URL, hasher hashid.Encoder, requester *e Name: name, ID: hashid.EncodeShareID(hasher, s.ID), Unlocked: unlocked, - Owner: user.BuildUserRedacted(owner, redactLevel, hasher), + Owner: user.BuildUserRedacted(ctx, owner, redactLevel, hasher), Expired: inventory.IsShareExpired(s) != nil || expired, Url: BuildShareLink(s, hasher, base, unlocked), CreatedAt: s.CreatedAt, @@ -446,7 +446,7 @@ func BuildExtendedInfo(ctx context.Context, u *ent.User, f fs.File, hasher hashi StoragePolicy: BuildStoragePolicy(extendedInfo.StoragePolicy, hasher), StorageUsed: extendedInfo.StorageUsed, Entities: lo.Map(f.Entities(), func(e fs.Entity, index int) Entity { - return BuildEntity(extendedInfo, e, hasher) + return BuildEntity(ctx, extendedInfo, e, hasher) }), DirectLinks: lo.Map(extendedInfo.DirectLinks, func(d *ent.DirectLink, index int) DirectLink { return BuildDirectLink(d, hasher, base) @@ -456,7 +456,7 @@ func BuildExtendedInfo(ctx context.Context, u *ent.User, f fs.File, hasher hashi if u.ID == f.OwnerID() { // Only owner can see the shares settings. ext.Shares = lo.Map(extendedInfo.Shares, func(s *ent.Share, index int) Share { - return *BuildShare(s, base, hasher, u, u, f.DisplayName(), f.Type(), true, false) + return *BuildShare(ctx, s, base, hasher, u, u, f.DisplayName(), f.Type(), true, false) }) ext.View = extendedInfo.View } @@ -473,11 +473,11 @@ func BuildDirectLink(d *ent.DirectLink, hasher hashid.Encoder, base *url.URL) Di } } -func BuildEntity(extendedInfo *fs.FileExtendedInfo, e fs.Entity, hasher hashid.Encoder) Entity { +func BuildEntity(ctx context.Context, extendedInfo *fs.FileExtendedInfo, e fs.Entity, hasher hashid.Encoder) Entity { var u *user.User createdBy := e.CreatedBy() if createdBy != nil { - userRedacted := user.BuildUserRedacted(e.CreatedBy(), user.RedactLevelAnonymous, hasher) + userRedacted := user.BuildUserRedacted(ctx, e.CreatedBy(), user.RedactLevelAnonymous, hasher) u = &userRedacted } diff --git a/service/share/response.go b/service/share/response.go index 002c5547..46b3ee2d 100644 --- a/service/share/response.go +++ b/service/share/response.go @@ -1,6 +1,7 @@ package share import ( + "context" "net/url" "github.com/cloudreve/Cloudreve/v4/ent" @@ -18,7 +19,7 @@ type ListShareResponse struct { Pagination *inventory.PaginationResults `json:"pagination"` } -func BuildListShareResponse(res *inventory.ListShareResult, hasher hashid.Encoder, base *url.URL, requester *ent.User, unlocked bool) *ListShareResponse { +func BuildListShareResponse(ctx context.Context, res *inventory.ListShareResult, hasher hashid.Encoder, base *url.URL, requester *ent.User, unlocked bool) *ListShareResponse { var infos []explorer.Share for _, share := range res.Shares { expired := inventory.IsValidShare(share) != nil @@ -36,7 +37,7 @@ func BuildListShareResponse(res *inventory.ListShareResult, hasher hashid.Encode } } - infos = append(infos, *explorer.BuildShare(share, base, hasher, requester, share.Edges.User, shareName, + infos = append(infos, *explorer.BuildShare(ctx, share, base, hasher, requester, share.Edges.User, shareName, types.FileType(share.Edges.File.Type), unlocked, expired)) } diff --git a/service/share/visit.go b/service/share/visit.go index c47f10fb..51168a93 100644 --- a/service/share/visit.go +++ b/service/share/visit.go @@ -87,7 +87,7 @@ func (s *ShareInfoService) Get(c *gin.Context) (*explorer.Share, error) { } base := dep.SettingProvider().SiteURL(c) - res := explorer.BuildShare(share, base, dep.HashIDEncoder(), u, share.Edges.User, share.Edges.File.Name, + res := explorer.BuildShare(c, share, base, dep.HashIDEncoder(), u, share.Edges.User, share.Edges.File.Name, types.FileType(share.Edges.File.Type), unlocked, false) if s.OwnerExtended && share.Edges.User.ID == u.ID { @@ -148,7 +148,7 @@ func (s *ListShareService) List(c *gin.Context) (*ListShareResponse, error) { } base := dep.SettingProvider().SiteURL(ctx) - return BuildListShareResponse(res, hasher, base, user, true), nil + return BuildListShareResponse(ctx, res, hasher, base, user, true), nil } func (s *ListShareService) ListInUserProfile(c *gin.Context, uid int) (*ListShareResponse, error) { @@ -188,5 +188,5 @@ func (s *ListShareService) ListInUserProfile(c *gin.Context, uid int) (*ListShar } base := dep.SettingProvider().SiteURL(ctx) - return BuildListShareResponse(res, hasher, base, user, false), nil + return BuildListShareResponse(ctx, res, hasher, base, user, false), nil } diff --git a/service/user/response.go b/service/user/response.go index ce359c80..ac063a0d 100644 --- a/service/user/response.go +++ b/service/user/response.go @@ -1,9 +1,11 @@ package user import ( + "context" "fmt" "time" + "github.com/cloudreve/Cloudreve/v4/application/dependency" "github.com/cloudreve/Cloudreve/v4/ent" "github.com/cloudreve/Cloudreve/v4/ent/user" "github.com/cloudreve/Cloudreve/v4/inventory/types" @@ -222,7 +224,9 @@ const ( ) // BuildUserRedacted Serialize a user without sensitive information. -func BuildUserRedacted(u *ent.User, level int, idEncoder hashid.Encoder) User { +// The email is only exposed when the requester is a signed-in user (level >= +// RedactLevelUser) and the `expose_user_email` setting is enabled. +func BuildUserRedacted(ctx context.Context, u *ent.User, level int, idEncoder hashid.Encoder) User { userRaw := BuildUser(u, idEncoder) user := User{ @@ -237,7 +241,7 @@ func BuildUserRedacted(u *ent.User, level int, idEncoder hashid.Encoder) User { user.Group = RedactedGroup(userRaw.Group) } - if level == RedactLevelUser { + if level == RedactLevelUser && dependency.FromContext(ctx).SettingProvider().ExposeUserEmail(ctx) { user.Email = userRaw.Email }