feat(user): options to disable exposing user emails to other users

dependabot/go_modules/golang.org/x/net-0.55.0
Aaron Liu 1 month ago
parent 9e9fb43e72
commit ba2e870bbd

@ -1 +1 @@
Subproject commit 0b722766f8808b0a0aaa3c9f3e4766972d746d22
Subproject commit 807360a8a6646a1c4bf8f1151e75da17a3eb457c

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

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

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

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

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

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

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

Loading…
Cancel
Save