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")
|
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
|
// Check authorization: either auth information or SID must be present
|
||||||
authMethod, secret := getHttpAuth(req)
|
authMethod, secret := getHttpAuth(req)
|
||||||
principal, challenge, err := authFileRequest(authMethod, secret, req.FormValue("sid"), getRemoteAddr(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)
|
writeHttpResponse(InfoChallenge(msgID, now, challenge), nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if principal.UID.IsZero() && req.FormValue("topic") != "newacc" {
|
if principal.UID.IsZero() && uploadTopic != "newacc" {
|
||||||
// Not authenticated and not signup.
|
// Not authenticated and not signup.
|
||||||
writeHttpResponse(ErrAuthRequired(msgID, "", now, now), nil)
|
writeHttpResponse(ErrAuthRequired(msgID, "", now, now), nil)
|
||||||
return
|
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.
|
// Check if uploads are handled elsewhere.
|
||||||
headers, statusCode, err := mh.Headers(principal.TenantID, req.Method, req.URL, req.Header, true)
|
headers, statusCode, err := mh.Headers(principal.TenantID, req.Method, req.URL, req.Header, true)
|
||||||
if err != nil {
|
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)
|
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()}
|
params := map[string]any{"url": url, "file_id": fdef.Uid().String()}
|
||||||
if globals.mediaGcPeriod > 0 {
|
if globals.mediaGcPeriod > 0 {
|
||||||
// How long this file is guaranteed to exist without being attached to a message or a topic.
|
// 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)
|
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 {
|
func authorizeLargeFileDownload(tenantID types.TenantID, uid types.Uid, rawURL string, fd *types.FileDef) error {
|
||||||
if fd == nil {
|
if fd == nil {
|
||||||
return types.ErrNotFound
|
return types.ErrNotFound
|
||||||
|
|||||||
Reference in New Issue
Block a user