diff --git a/server/hdl_files.go b/server/hdl_files.go index 477dbe1..b305f8c 100644 --- a/server/hdl_files.go +++ b/server/hdl_files.go @@ -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