feat(server): 支持上传文件关联会话话题并做权限校验
新增上传时读取会话话题参数,校验上传者是否为该话题的成员,通过后将文件与话题关联,允许其他成员下载。权限校验仅针对点对点和群组话题,关联失败不会阻止文件上传。
This commit is contained in:
+38
-1
@@ -239,6 +239,9 @@ func largeFileReceiveHTTP(wrt http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
msgID := req.FormValue("id")
|
||||
// When the uploader passes a conversation topic, the file is linked to it so other
|
||||
// members of that conversation can download it (see TenantFileUserHasAccess).
|
||||
uploadTopic := strings.TrimSpace(req.FormValue("topic"))
|
||||
// Check authorization: either auth information or SID must be present
|
||||
authMethod, secret := getHttpAuth(req)
|
||||
principal, challenge, err := authFileRequest(authMethod, secret, req.FormValue("sid"), getRemoteAddr(req))
|
||||
@@ -250,11 +253,17 @@ func largeFileReceiveHTTP(wrt http.ResponseWriter, req *http.Request) {
|
||||
writeHttpResponse(InfoChallenge(msgID, now, challenge), nil)
|
||||
return
|
||||
}
|
||||
if principal.UID.IsZero() && req.FormValue("topic") != "newacc" {
|
||||
if principal.UID.IsZero() && uploadTopic != "newacc" {
|
||||
// Not authenticated and not signup.
|
||||
writeHttpResponse(ErrAuthRequired(msgID, "", now, now), nil)
|
||||
return
|
||||
}
|
||||
if !principal.UID.IsZero() && uploadTopic != "" {
|
||||
if err = authorizeUploadTopicLink(principal.TenantID, principal.UID, uploadTopic); err != nil {
|
||||
writeHttpResponse(decodeStoreError(err, msgID, now, nil), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Check if uploads are handled elsewhere.
|
||||
headers, statusCode, err := mh.Headers(principal.TenantID, req.Method, req.URL, req.Header, true)
|
||||
if err != nil {
|
||||
@@ -361,6 +370,13 @@ func largeFileReceiveHTTP(wrt http.ResponseWriter, req *http.Request) {
|
||||
logs.Warn.Printf("media upload: failed to enqueue file lifecycle event file=%s: %v", fdef.Id, err)
|
||||
}
|
||||
|
||||
if uploadTopic != "" {
|
||||
if err = store.Files.LinkAttachments(principal.TenantID, uploadTopic, types.ZeroUid, []string{fdef.Uid().String()}); err != nil {
|
||||
// The file itself is valid; linking is best-effort so do not fail the upload.
|
||||
logs.Warn.Printf("media upload: failed to link file %s to topic %s: %v", fdef.Id, uploadTopic, err)
|
||||
}
|
||||
}
|
||||
|
||||
params := map[string]any{"url": url, "file_id": fdef.Uid().String()}
|
||||
if globals.mediaGcPeriod > 0 {
|
||||
// How long this file is guaranteed to exist without being attached to a message or a topic.
|
||||
@@ -484,6 +500,27 @@ func authorizeLargeFileRequestURL(tenantID types.TenantID, uid types.Uid, rawURL
|
||||
return authorizeLargeFileDownload(tenantID, uid, rawURL, fd)
|
||||
}
|
||||
|
||||
// authorizeUploadTopicLink verifies the uploader is a reading member of the conversation
|
||||
// topic, so the uploaded file may be linked to it and downloaded by other members.
|
||||
func authorizeUploadTopicLink(tenantID types.TenantID, uid types.Uid, topic string) error {
|
||||
switch types.GetTopicCat(topic) {
|
||||
case types.TopicCatP2P, types.TopicCatGrp:
|
||||
default:
|
||||
return types.ErrPermissionDenied
|
||||
}
|
||||
if store.Store == nil || !store.Store.IsOpen() {
|
||||
return types.ErrUnsupported
|
||||
}
|
||||
sub, err := store.Subs.Get(tenantID, topic, uid, false)
|
||||
if err != nil || sub == nil || sub.DeletedAt != nil {
|
||||
return types.ErrPermissionDenied
|
||||
}
|
||||
if !sub.ModeWant.IsReader() || !sub.ModeGiven.IsReader() {
|
||||
return types.ErrPermissionDenied
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func authorizeLargeFileDownload(tenantID types.TenantID, uid types.Uid, rawURL string, fd *types.FileDef) error {
|
||||
if fd == nil {
|
||||
return types.ErrNotFound
|
||||
|
||||
Reference in New Issue
Block a user