file.go 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. package controller
  2. import (
  3. "context"
  4. "database/sql"
  5. "encoding/json"
  6. "errors"
  7. "runtime/debug"
  8. "strconv"
  9. "strings"
  10. "github.com/ente-io/museum/pkg/controller/email"
  11. "github.com/ente-io/museum/pkg/controller/lock"
  12. "github.com/ente-io/museum/pkg/utils/auth"
  13. "github.com/ente-io/museum/pkg/utils/file"
  14. "github.com/ente-io/stacktrace"
  15. "github.com/gin-contrib/requestid"
  16. "github.com/gin-gonic/gin"
  17. "github.com/google/uuid"
  18. "github.com/aws/aws-sdk-go/service/s3"
  19. "github.com/ente-io/museum/ente"
  20. "github.com/ente-io/museum/pkg/repo"
  21. enteArray "github.com/ente-io/museum/pkg/utils/array"
  22. "github.com/ente-io/museum/pkg/utils/s3config"
  23. "github.com/ente-io/museum/pkg/utils/time"
  24. log "github.com/sirupsen/logrus"
  25. )
  26. // FileController exposes functions to retrieve and access encrypted files
  27. type FileController struct {
  28. FileRepo *repo.FileRepository
  29. ObjectRepo *repo.ObjectRepository
  30. ObjectCleanupRepo *repo.ObjectCleanupRepository
  31. TrashRepository *repo.TrashRepository
  32. UserRepo *repo.UserRepository
  33. UsageCtrl *UsageController
  34. CollectionRepo *repo.CollectionRepository
  35. TaskLockingRepo *repo.TaskLockRepository
  36. QueueRepo *repo.QueueRepository
  37. S3Config *s3config.S3Config
  38. ObjectCleanupCtrl *ObjectCleanupController
  39. LockController *lock.LockController
  40. EmailNotificationCtrl *email.EmailNotificationController
  41. HostName string
  42. cleanupCronRunning bool
  43. }
  44. // StorageOverflowAboveSubscriptionLimit is the amount (50 MB) by which user can go beyond their storage limit
  45. const StorageOverflowAboveSubscriptionLimit = int64(1024 * 1024 * 50)
  46. // MaxFileSize is the maximum file size a user can upload
  47. const MaxFileSize = int64(1024 * 1024 * 1024 * 5)
  48. // MaxUploadURLsLimit indicates the max number of upload urls which can be request in one go
  49. const MaxUploadURLsLimit = 50
  50. // Create adds an entry for a file in the respective tables
  51. func (c *FileController) Create(ctx context.Context, userID int64, file ente.File, userAgent string, app ente.App) (ente.File, error) {
  52. objectPathPrefix := strconv.FormatInt(userID, 10) + "/"
  53. if !strings.HasPrefix(file.File.ObjectKey, objectPathPrefix) || !strings.HasPrefix(file.Thumbnail.ObjectKey, objectPathPrefix) {
  54. return file, stacktrace.Propagate(ente.ErrBadRequest, "Incorrect object key reported")
  55. }
  56. collection, err := c.CollectionRepo.Get(file.CollectionID)
  57. if err != nil {
  58. return file, stacktrace.Propagate(err, "")
  59. }
  60. // Verify that user owns the collection.
  61. // Warning: Do not remove this check
  62. if collection.Owner.ID != userID || file.OwnerID != userID {
  63. return file, stacktrace.Propagate(ente.ErrPermissionDenied, "")
  64. }
  65. if collection.IsDeleted {
  66. return file, stacktrace.Propagate(ente.ErrNotFound, "collection has been deleted")
  67. }
  68. hotDC := c.S3Config.GetHotDataCenter()
  69. // sizeOf will do also HEAD check to ensure that the object exists in the
  70. // current hot DC
  71. fileSize, err := c.sizeOf(file.File.ObjectKey)
  72. if err != nil {
  73. log.Error("Could not find size of file: " + file.File.ObjectKey)
  74. return file, stacktrace.Propagate(err, "")
  75. }
  76. if fileSize > MaxFileSize {
  77. return file, stacktrace.Propagate(ente.ErrFileTooLarge, "")
  78. }
  79. if file.File.Size != 0 && file.File.Size != fileSize {
  80. return file, stacktrace.Propagate(ente.ErrBadRequest, "mismatch in file size")
  81. }
  82. file.File.Size = fileSize
  83. thumbnailSize, err := c.sizeOf(file.Thumbnail.ObjectKey)
  84. if err != nil {
  85. log.Error("Could not find size of thumbnail: " + file.Thumbnail.ObjectKey)
  86. return file, stacktrace.Propagate(err, "")
  87. }
  88. if file.Thumbnail.Size != 0 && file.Thumbnail.Size != thumbnailSize {
  89. return file, stacktrace.Propagate(ente.ErrBadRequest, "mismatch in thumbnail size")
  90. }
  91. file.Thumbnail.Size = thumbnailSize
  92. var totalUploadSize = fileSize + thumbnailSize
  93. err = c.UsageCtrl.CanUploadFile(ctx, userID, &totalUploadSize, app)
  94. if err != nil {
  95. return file, stacktrace.Propagate(err, "")
  96. }
  97. file.Info = &ente.FileInfo{
  98. FileSize: fileSize,
  99. ThumbnailSize: thumbnailSize,
  100. }
  101. // all iz well
  102. var usage int64
  103. file, usage, err = c.FileRepo.Create(file, fileSize, thumbnailSize, fileSize+thumbnailSize, collection.Owner.ID, app)
  104. if err != nil {
  105. if err == ente.ErrDuplicateFileObjectFound || err == ente.ErrDuplicateThumbnailObjectFound {
  106. var existing ente.File
  107. if err == ente.ErrDuplicateFileObjectFound {
  108. existing, err = c.FileRepo.GetFileAttributesFromObjectKey(file.File.ObjectKey)
  109. } else {
  110. existing, err = c.FileRepo.GetFileAttributesFromObjectKey(file.Thumbnail.ObjectKey)
  111. }
  112. if err != nil {
  113. return file, stacktrace.Propagate(err, "")
  114. }
  115. file, err = c.onDuplicateObjectDetected(file, existing, hotDC)
  116. if err != nil {
  117. return file, stacktrace.Propagate(err, "")
  118. }
  119. return file, nil
  120. }
  121. return file, stacktrace.Propagate(err, "")
  122. }
  123. if usage == fileSize+thumbnailSize {
  124. go c.EmailNotificationCtrl.OnFirstFileUpload(file.OwnerID, userAgent)
  125. }
  126. return file, nil
  127. }
  128. // Update verifies permissions and updates the specified file
  129. func (c *FileController) Update(ctx context.Context, userID int64, file ente.File, app ente.App) (ente.UpdateFileResponse, error) {
  130. var response ente.UpdateFileResponse
  131. objectPathPrefix := strconv.FormatInt(userID, 10) + "/"
  132. if !strings.HasPrefix(file.File.ObjectKey, objectPathPrefix) || !strings.HasPrefix(file.Thumbnail.ObjectKey, objectPathPrefix) {
  133. return response, stacktrace.Propagate(ente.ErrBadRequest, "Incorrect object key reported")
  134. }
  135. ownerID, err := c.FileRepo.GetOwnerID(file.ID)
  136. if err != nil {
  137. return response, stacktrace.Propagate(err, "")
  138. }
  139. // verify that user owns the file
  140. if ownerID != userID {
  141. return response, stacktrace.Propagate(ente.ErrPermissionDenied, "")
  142. }
  143. file.OwnerID = ownerID
  144. existingFileObject, err := c.ObjectRepo.GetObject(file.ID, ente.FILE)
  145. if err != nil {
  146. return response, stacktrace.Propagate(err, "")
  147. }
  148. existingFileObjectKey := existingFileObject.ObjectKey
  149. oldFileSize := existingFileObject.FileSize
  150. existingThumbnailObject, err := c.ObjectRepo.GetObject(file.ID, ente.THUMBNAIL)
  151. if err != nil {
  152. return response, stacktrace.Propagate(err, "")
  153. }
  154. existingThumbnailObjectKey := existingThumbnailObject.ObjectKey
  155. oldThumbnailSize := existingThumbnailObject.FileSize
  156. fileSize, err := c.sizeOf(file.File.ObjectKey)
  157. if err != nil {
  158. return response, stacktrace.Propagate(err, "")
  159. }
  160. if fileSize > MaxFileSize {
  161. return response, stacktrace.Propagate(ente.ErrFileTooLarge, "")
  162. }
  163. if file.File.Size != 0 && file.File.Size != fileSize {
  164. return response, stacktrace.Propagate(ente.ErrBadRequest, "mismatch in file size")
  165. }
  166. thumbnailSize, err := c.sizeOf(file.Thumbnail.ObjectKey)
  167. if err != nil {
  168. return response, stacktrace.Propagate(err, "")
  169. }
  170. if file.Thumbnail.Size != 0 && file.Thumbnail.Size != thumbnailSize {
  171. return response, stacktrace.Propagate(ente.ErrBadRequest, "mismatch in thumbnail size")
  172. }
  173. diff := (fileSize + thumbnailSize) - (oldFileSize + oldThumbnailSize)
  174. err = c.UsageCtrl.CanUploadFile(ctx, userID, &diff, app)
  175. if err != nil {
  176. return response, stacktrace.Propagate(err, "")
  177. }
  178. // The client might retry updating the same file accidentally.
  179. //
  180. // This usually happens on iOS, where the first request to update a file
  181. // might succeed, but the client might go into the background before it gets
  182. // to know of it, and then retries again.
  183. //
  184. // As a safety check, also compare the file sizes.
  185. isDuplicateRequest := false
  186. if existingThumbnailObjectKey == file.Thumbnail.ObjectKey &&
  187. existingFileObjectKey == file.File.ObjectKey &&
  188. diff == 0 {
  189. isDuplicateRequest = true
  190. }
  191. oldObjects := make([]string, 0)
  192. if existingThumbnailObjectKey != file.Thumbnail.ObjectKey {
  193. // Ignore accidental retrials
  194. oldObjects = append(oldObjects, existingThumbnailObjectKey)
  195. }
  196. if existingFileObjectKey != file.File.ObjectKey {
  197. // Ignore accidental retrials
  198. oldObjects = append(oldObjects, existingFileObjectKey)
  199. }
  200. if file.Info != nil {
  201. file.Info.FileSize = fileSize
  202. file.Info.ThumbnailSize = thumbnailSize
  203. } else {
  204. file.Info = &ente.FileInfo{
  205. FileSize: fileSize,
  206. ThumbnailSize: thumbnailSize,
  207. }
  208. }
  209. err = c.FileRepo.Update(file, fileSize, thumbnailSize, diff, oldObjects, isDuplicateRequest)
  210. if err != nil {
  211. return response, stacktrace.Propagate(err, "")
  212. }
  213. response.ID = file.ID
  214. response.UpdationTime = file.UpdationTime
  215. return response, nil
  216. }
  217. // GetUploadURLs returns a bunch of presigned URLs for uploading files
  218. func (c *FileController) GetUploadURLs(ctx context.Context, userID int64, count int, app ente.App) ([]ente.UploadURL, error) {
  219. err := c.UsageCtrl.CanUploadFile(ctx, userID, nil, app)
  220. if err != nil {
  221. return []ente.UploadURL{}, stacktrace.Propagate(err, "")
  222. }
  223. s3Client := c.S3Config.GetHotS3Client()
  224. dc := c.S3Config.GetHotDataCenter()
  225. bucket := c.S3Config.GetHotBucket()
  226. urls := make([]ente.UploadURL, 0)
  227. objectKeys := make([]string, 0)
  228. if count > MaxUploadURLsLimit {
  229. count = MaxUploadURLsLimit
  230. }
  231. for i := 0; i < count; i++ {
  232. objectKey := strconv.FormatInt(userID, 10) + "/" + uuid.NewString()
  233. objectKeys = append(objectKeys, objectKey)
  234. url, err := c.getObjectURL(s3Client, dc, bucket, objectKey)
  235. if err != nil {
  236. return urls, stacktrace.Propagate(err, "")
  237. }
  238. urls = append(urls, url)
  239. }
  240. log.Print("Returning objectKeys: " + strings.Join(objectKeys, ", "))
  241. return urls, nil
  242. }
  243. // GetFileURL verifies permissions and returns a presigned url to the requested file
  244. func (c *FileController) GetFileURL(userID int64, fileID int64) (string, error) {
  245. err := c.verifyFileAccess(userID, fileID)
  246. if err != nil {
  247. return "", stacktrace.Propagate(err, "")
  248. }
  249. url, err := c.getSignedURLForType(fileID, ente.FILE)
  250. if err != nil {
  251. if errors.Is(err, sql.ErrNoRows) {
  252. go c.CleanUpStaleCollectionFiles(userID, fileID)
  253. }
  254. return "", stacktrace.Propagate(err, "")
  255. }
  256. return url, nil
  257. }
  258. // GetThumbnailURL verifies permissions and returns a presigned url to the requested thumbnail
  259. func (c *FileController) GetThumbnailURL(userID int64, fileID int64) (string, error) {
  260. err := c.verifyFileAccess(userID, fileID)
  261. if err != nil {
  262. return "", stacktrace.Propagate(err, "")
  263. }
  264. url, err := c.getSignedURLForType(fileID, ente.THUMBNAIL)
  265. if err != nil {
  266. if errors.Is(err, sql.ErrNoRows) {
  267. go c.CleanUpStaleCollectionFiles(userID, fileID)
  268. }
  269. return "", stacktrace.Propagate(err, "")
  270. }
  271. return url, nil
  272. }
  273. func (c *FileController) CleanUpStaleCollectionFiles(userID int64, fileID int64) {
  274. logger := log.WithFields(log.Fields{
  275. "userID": userID,
  276. "fileID": fileID,
  277. "action": "CleanUpStaleCollectionFiles",
  278. })
  279. // catch panic
  280. defer func() {
  281. if r := recover(); r != nil {
  282. logger.Error("Recovered from panic", r)
  283. }
  284. }()
  285. fileIDs := make([]int64, 0)
  286. fileIDs = append(fileIDs, fileID)
  287. // verify file ownership
  288. err := c.FileRepo.VerifyFileOwner(context.Background(), fileIDs, userID, logger)
  289. if err != nil {
  290. logger.Warning("Failed to verify file ownership", err)
  291. return
  292. }
  293. err = c.TrashRepository.CleanUpDeletedFilesFromCollection(context.Background(), fileIDs, userID)
  294. if err != nil {
  295. logger.WithError(err).Error("Failed to clean up stale files from collection")
  296. }
  297. }
  298. // GetPublicFileURL verifies permissions and returns a presigned url to the requested file
  299. func (c *FileController) GetPublicFileURL(ctx *gin.Context, fileID int64, objType ente.ObjectType) (string, error) {
  300. accessContext := auth.MustGetPublicAccessContext(ctx)
  301. accessible, err := c.CollectionRepo.DoesFileExistInCollections(fileID, []int64{accessContext.CollectionID})
  302. if err != nil {
  303. return "", stacktrace.Propagate(err, "")
  304. }
  305. if !accessible {
  306. return "", stacktrace.Propagate(ente.ErrPermissionDenied, "")
  307. }
  308. return c.getSignedURLForType(fileID, objType)
  309. }
  310. // GetCastFileUrl verifies permissions and returns a presigned url to the requested file
  311. func (c *FileController) GetCastFileUrl(ctx *gin.Context, fileID int64, objType ente.ObjectType) (string, error) {
  312. castCtx := auth.GetCastCtx(ctx)
  313. accessible, err := c.CollectionRepo.DoesFileExistInCollections(fileID, []int64{castCtx.CollectionID})
  314. if err != nil {
  315. return "", stacktrace.Propagate(err, "")
  316. }
  317. if !accessible {
  318. return "", stacktrace.Propagate(ente.ErrPermissionDenied, "")
  319. }
  320. return c.getSignedURLForType(fileID, objType)
  321. }
  322. func (c *FileController) getSignedURLForType(fileID int64, objType ente.ObjectType) (string, error) {
  323. s3Object, err := c.ObjectRepo.GetObject(fileID, objType)
  324. if err != nil {
  325. return "", stacktrace.Propagate(err, "")
  326. }
  327. return c.getPreSignedURL(s3Object.ObjectKey)
  328. }
  329. // Trash deletes file and move them to trash
  330. func (c *FileController) Trash(ctx *gin.Context, userID int64, request ente.TrashRequest) error {
  331. fileIDs := make([]int64, 0)
  332. collectionIDs := make([]int64, 0)
  333. for _, trashItem := range request.TrashItems {
  334. fileIDs = append(fileIDs, trashItem.FileID)
  335. collectionIDs = append(collectionIDs, trashItem.CollectionID)
  336. }
  337. if enteArray.ContainsDuplicateInInt64Array(fileIDs) {
  338. return stacktrace.Propagate(ente.ErrBadRequest, "duplicate fileIDs")
  339. }
  340. if err := c.VerifyFileOwnership(ctx, userID, fileIDs); err != nil {
  341. return stacktrace.Propagate(err, "")
  342. }
  343. uniqueCollectionIDs := enteArray.UniqueInt64(collectionIDs)
  344. for _, collectionID := range uniqueCollectionIDs {
  345. ownerID, err := c.CollectionRepo.GetOwnerID(collectionID)
  346. if err != nil {
  347. return stacktrace.Propagate(err, "")
  348. }
  349. if ownerID != userID {
  350. return stacktrace.Propagate(ente.ErrPermissionDenied, "user doesn't own collection")
  351. }
  352. }
  353. return c.TrashRepository.TrashFiles(fileIDs, userID, request)
  354. }
  355. // GetSize returns the size of files indicated by fileIDs that are owned by userID
  356. func (c *FileController) GetSize(userID int64, fileIDs []int64) (int64, error) {
  357. size, err := c.FileRepo.GetSize(userID, fileIDs)
  358. if err != nil {
  359. return -1, stacktrace.Propagate(err, "")
  360. }
  361. return size, nil
  362. }
  363. // GetFileInfo returns the file infos given list of files
  364. func (c *FileController) GetFileInfo(ctx *gin.Context, userID int64, fileIDs []int64) (*ente.FilesInfoResponse, error) {
  365. logger := log.WithFields(log.Fields{
  366. "req_id": requestid.Get(ctx),
  367. })
  368. err := c.FileRepo.VerifyFileOwner(ctx, fileIDs, userID, logger)
  369. if err != nil {
  370. return nil, stacktrace.Propagate(err, "")
  371. }
  372. // Use GetFilesInfo for get fileInfo for the given list.
  373. // Then for fileIDs that are not present in the response of GetFilesInfo, use GetFileInfoFromObjectKeys to get the file info.
  374. // and merge the two responses. and for the fileIDs that are not present in the response of GetFileInfoFromObjectKeys,
  375. // add a new FileInfo entry with size = -1
  376. fileInfoResponse, err := c.FileRepo.GetFilesInfo(ctx, fileIDs, userID)
  377. if err != nil {
  378. return nil, stacktrace.Propagate(err, "")
  379. }
  380. fileIDsNotPresentInFilesDB := make([]int64, 0)
  381. for _, fileID := range fileIDs {
  382. if val, ok := fileInfoResponse[fileID]; !ok || val == nil {
  383. fileIDsNotPresentInFilesDB = append(fileIDsNotPresentInFilesDB, fileID)
  384. }
  385. }
  386. if len(fileIDsNotPresentInFilesDB) > 0 {
  387. logger.WithField("count", len(fileIDsNotPresentInFilesDB)).Info("fileInfos are not present in files table, fetching from object keys")
  388. fileInfoResponseFromObjectKeys, err := c.FileRepo.GetFileInfoFromObjectKeys(ctx, fileIDsNotPresentInFilesDB)
  389. if err != nil {
  390. return nil, stacktrace.Propagate(err, "")
  391. }
  392. err = c.FileRepo.UpdateSizeInfo(ctx, fileInfoResponseFromObjectKeys)
  393. if err != nil {
  394. return nil, stacktrace.Propagate(err, "Failed to update the size info in files")
  395. }
  396. for id, fileInfo := range fileInfoResponseFromObjectKeys {
  397. fileInfoResponse[id] = fileInfo
  398. }
  399. }
  400. missedFileIDs := make([]int64, 0)
  401. for _, fileID := range fileIDs {
  402. if _, ok := fileInfoResponse[fileID]; !ok {
  403. missedFileIDs = append(missedFileIDs, fileID)
  404. }
  405. }
  406. if len(missedFileIDs) > 0 {
  407. return nil, stacktrace.Propagate(ente.NewInternalError("failed to get fileInfo"), "fileIDs not found: %v", missedFileIDs)
  408. }
  409. // prepare a list of FileInfoResponse
  410. fileInfoList := make([]*ente.FileInfoResponse, 0)
  411. for _, fileID := range fileIDs {
  412. fileInfoList = append(fileInfoList, &ente.FileInfoResponse{
  413. ID: fileID,
  414. FileInfo: *fileInfoResponse[fileID],
  415. })
  416. }
  417. return &ente.FilesInfoResponse{
  418. FilesInfo: fileInfoList,
  419. }, nil
  420. }
  421. // GetDuplicates returns the list of files of the same size
  422. func (c *FileController) GetDuplicates(userID int64) ([]ente.DuplicateFiles, error) {
  423. dupes, err := c.FileRepo.GetDuplicateFiles(userID)
  424. if err != nil {
  425. return nil, stacktrace.Propagate(err, "")
  426. }
  427. return dupes, nil
  428. }
  429. // GetLargeThumbnailFiles returns the list of files whose thumbnail size is larger than threshold size
  430. func (c *FileController) GetLargeThumbnailFiles(userID int64, threshold int64) ([]int64, error) {
  431. largeThumbnailFiles, err := c.FileRepo.GetLargeThumbnailFiles(userID, threshold)
  432. if err != nil {
  433. return nil, stacktrace.Propagate(err, "")
  434. }
  435. return largeThumbnailFiles, nil
  436. }
  437. // UpdateMagicMetadata updates the magic metadata for list of files
  438. func (c *FileController) UpdateMagicMetadata(ctx *gin.Context, req ente.UpdateMultipleMagicMetadataRequest, isPublicMetadata bool) error {
  439. err := c.validateUpdateMetadataRequest(ctx, req, isPublicMetadata)
  440. if err != nil {
  441. return stacktrace.Propagate(err, "")
  442. }
  443. err = c.FileRepo.UpdateMagicAttributes(ctx, req.MetadataList, isPublicMetadata)
  444. if err != nil {
  445. return stacktrace.Propagate(err, "failed to update magic attributes")
  446. }
  447. return nil
  448. }
  449. // UpdateThumbnail updates thumbnail of a file
  450. func (c *FileController) UpdateThumbnail(ctx *gin.Context, fileID int64, newThumbnail ente.FileAttributes, app ente.App) error {
  451. userID := auth.GetUserID(ctx.Request.Header)
  452. objectPathPrefix := strconv.FormatInt(userID, 10) + "/"
  453. if !strings.HasPrefix(newThumbnail.ObjectKey, objectPathPrefix) {
  454. return stacktrace.Propagate(ente.ErrBadRequest, "Incorrect object key reported")
  455. }
  456. ownerID, err := c.FileRepo.GetOwnerID(fileID)
  457. if err != nil {
  458. return stacktrace.Propagate(err, "")
  459. }
  460. // verify that user owns the file
  461. if ownerID != userID {
  462. return stacktrace.Propagate(ente.ErrPermissionDenied, "")
  463. }
  464. existingThumbnailObject, err := c.ObjectRepo.GetObject(fileID, ente.THUMBNAIL)
  465. if err != nil {
  466. return stacktrace.Propagate(err, "")
  467. }
  468. existingThumbnailObjectKey := existingThumbnailObject.ObjectKey
  469. oldThumbnailSize := existingThumbnailObject.FileSize
  470. newThumbnailSize, err := c.sizeOf(newThumbnail.ObjectKey)
  471. if err != nil {
  472. return stacktrace.Propagate(err, "")
  473. }
  474. diff := newThumbnailSize - oldThumbnailSize
  475. if diff > 0 {
  476. return stacktrace.Propagate(errors.New("new thumbnail larger than existing thumbnail"), "")
  477. }
  478. err = c.UsageCtrl.CanUploadFile(ctx, userID, &diff, app)
  479. if err != nil {
  480. return stacktrace.Propagate(err, "")
  481. }
  482. var oldObject *string
  483. if existingThumbnailObjectKey != newThumbnail.ObjectKey {
  484. // delete old object only if newThumbnail object key different.
  485. oldObject = &existingThumbnailObjectKey
  486. }
  487. err = c.FileRepo.UpdateThumbnail(ctx, fileID, userID, newThumbnail, newThumbnailSize, diff, oldObject)
  488. if err != nil {
  489. return stacktrace.Propagate(err, "")
  490. }
  491. return nil
  492. }
  493. // VerifyFileOwnership will return error if given fileIDs are not valid or don't belong to the ownerID
  494. func (c *FileController) VerifyFileOwnership(ctx *gin.Context, ownerID int64, fileIDs []int64) error {
  495. countMap, err := c.FileRepo.GetOwnerToFileCountMap(ctx, fileIDs)
  496. if err != nil {
  497. return stacktrace.Propagate(err, "failed to get owners info")
  498. }
  499. logger := log.WithFields(log.Fields{
  500. "req_id": requestid.Get(ctx),
  501. "owner_id": ownerID,
  502. "file_ids": fileIDs,
  503. "owners_map": countMap,
  504. })
  505. if len(countMap) == 0 {
  506. logger.Error("all fileIDs are invalid")
  507. return stacktrace.Propagate(ente.ErrBadRequest, "")
  508. }
  509. if len(countMap) > 1 {
  510. logger.Error("files are owned by multiple users")
  511. return stacktrace.Propagate(ente.ErrPermissionDenied, "")
  512. }
  513. if filesOwned, ok := countMap[ownerID]; ok {
  514. if filesOwned != int64(len(fileIDs)) {
  515. logger.WithField("file_owned", filesOwned).Error("failed to find all fileIDs")
  516. return stacktrace.Propagate(ente.ErrBadRequest, "")
  517. }
  518. return nil
  519. } else {
  520. logger.Error("user is not an owner of any file")
  521. return stacktrace.Propagate(ente.ErrPermissionDenied, "")
  522. }
  523. }
  524. func (c *FileController) validateUpdateMetadataRequest(ctx *gin.Context, req ente.UpdateMultipleMagicMetadataRequest, isPublicMetadata bool) error {
  525. userID := auth.GetUserID(ctx.Request.Header)
  526. for _, updateMMdRequest := range req.MetadataList {
  527. ownerID, existingMetadata, err := c.FileRepo.GetOwnerAndMagicMetadata(updateMMdRequest.ID, isPublicMetadata)
  528. if err != nil {
  529. return stacktrace.Propagate(err, "")
  530. }
  531. if ownerID != userID {
  532. log.WithFields(log.Fields{
  533. "file_id": updateMMdRequest.ID,
  534. "owner_id": ownerID,
  535. "user_id": userID,
  536. "public_md": isPublicMetadata,
  537. }).Error("can't update magic metadata for file which isn't owned by use")
  538. return stacktrace.Propagate(ente.ErrPermissionDenied, "")
  539. }
  540. if existingMetadata != nil && (existingMetadata.Version != updateMMdRequest.MagicMetadata.Version || existingMetadata.Count > updateMMdRequest.MagicMetadata.Count) {
  541. log.WithFields(log.Fields{
  542. "existing_count": existingMetadata.Count,
  543. "existing_version": existingMetadata.Version,
  544. "file_id": updateMMdRequest.ID,
  545. "received_count": updateMMdRequest.MagicMetadata.Count,
  546. "received_version": updateMMdRequest.MagicMetadata.Version,
  547. "public_md": isPublicMetadata,
  548. }).Error("invalid ops: mismatch in metadata version or count")
  549. return stacktrace.Propagate(ente.ErrVersionMismatch, "mismatch in metadata version or count")
  550. }
  551. }
  552. return nil
  553. }
  554. // CleanupDeletedFiles deletes the files from object store. It will delete from both hot storage and
  555. // cold storage (if replicated)
  556. func (c *FileController) CleanupDeletedFiles() {
  557. log.Info("Cleaning up deleted files")
  558. // If cleanup is already running, avoiding concurrent runs to avoid concurrent issues
  559. if c.cleanupCronRunning {
  560. log.Info("Skipping CleanupDeletedFiles cron run as another instance is still running")
  561. return
  562. }
  563. c.cleanupCronRunning = true
  564. defer func() {
  565. c.cleanupCronRunning = false
  566. }()
  567. items, err := c.QueueRepo.GetItemsReadyForDeletion(repo.DeleteObjectQueue, 200)
  568. if err != nil {
  569. log.WithError(err).Error("Failed to fetch items from queue")
  570. return
  571. }
  572. for _, i := range items {
  573. c.cleanupDeletedFile(i)
  574. }
  575. }
  576. func (c *FileController) GetTotalFileCount() (int64, error) {
  577. count, err := c.FileRepo.GetTotalFileCount()
  578. if err != nil {
  579. return -1, stacktrace.Propagate(err, "")
  580. }
  581. return count, nil
  582. }
  583. func (c *FileController) cleanupDeletedFile(qItem repo.QueueItem) {
  584. lockName := file.GetLockNameForObject(qItem.Item)
  585. lockStatus, err := c.TaskLockingRepo.AcquireLock(lockName, time.MicrosecondsAfterHours(1), c.HostName)
  586. ctxLogger := log.WithField("item", qItem.Item).WithField("queue_id", qItem.Id)
  587. if err != nil || !lockStatus {
  588. ctxLogger.Warn("unable to acquire lock")
  589. return
  590. }
  591. defer func() {
  592. err = c.TaskLockingRepo.ReleaseLock(lockName)
  593. if err != nil {
  594. ctxLogger.Errorf("Error while releasing lock %s", err)
  595. }
  596. }()
  597. ctxLogger.Info("Deleting item")
  598. dcs, err := c.ObjectRepo.GetDataCentersForObject(qItem.Item)
  599. if err != nil {
  600. ctxLogger.Errorf("Could not fetch datacenters %s", err)
  601. return
  602. }
  603. for _, dc := range dcs {
  604. if c.S3Config.ShouldDeleteFromDataCenter(dc) {
  605. err = c.ObjectCleanupCtrl.DeleteObjectFromDataCenter(qItem.Item, dc)
  606. }
  607. if err != nil {
  608. ctxLogger.WithError(err).Error("Failed to delete " + qItem.Item + " from " + dc)
  609. return
  610. }
  611. err = c.ObjectRepo.RemoveDataCenterFromObject(qItem.Item, dc)
  612. if err != nil {
  613. ctxLogger.WithError(err).Error("Could not remove from table: " + qItem.Item + ", dc: " + dc)
  614. return
  615. }
  616. }
  617. err = c.ObjectRepo.RemoveObjectsForKey(qItem.Item)
  618. if err != nil {
  619. ctxLogger.WithError(err).Error("Failed to remove item from object_keys")
  620. return
  621. }
  622. err = c.QueueRepo.DeleteItem(repo.DeleteObjectQueue, qItem.Item)
  623. if err != nil {
  624. ctxLogger.WithError(err).Error("Failed to remove item from the queue")
  625. return
  626. }
  627. ctxLogger.Info("Successfully deleted item")
  628. }
  629. func (c *FileController) getPreSignedURL(objectKey string) (string, error) {
  630. s3Client := c.S3Config.GetHotS3Client()
  631. r, _ := s3Client.GetObjectRequest(&s3.GetObjectInput{
  632. Bucket: c.S3Config.GetHotBucket(),
  633. Key: &objectKey,
  634. })
  635. return r.Presign(PreSignedRequestValidityDuration)
  636. }
  637. func (c *FileController) sizeOf(objectKey string) (int64, error) {
  638. s3Client := c.S3Config.GetHotS3Client()
  639. head, err := s3Client.HeadObject(&s3.HeadObjectInput{
  640. Key: &objectKey,
  641. Bucket: c.S3Config.GetHotBucket(),
  642. })
  643. if err != nil {
  644. return -1, stacktrace.Propagate(err, "")
  645. }
  646. return *head.ContentLength, nil
  647. }
  648. func (c *FileController) onDuplicateObjectDetected(file ente.File, existing ente.File, hotDC string) (ente.File, error) {
  649. newJSON, _ := json.Marshal(file)
  650. existingJSON, _ := json.Marshal(existing)
  651. log.Info("Comparing " + string(newJSON) + " against " + string(existingJSON))
  652. if file.Thumbnail.ObjectKey == existing.Thumbnail.ObjectKey &&
  653. file.Thumbnail.Size == existing.Thumbnail.Size &&
  654. file.Thumbnail.DecryptionHeader == existing.Thumbnail.DecryptionHeader &&
  655. file.File.ObjectKey == existing.File.ObjectKey &&
  656. file.File.Size == existing.File.Size &&
  657. file.File.DecryptionHeader == existing.File.DecryptionHeader &&
  658. file.Metadata.EncryptedData == existing.Metadata.EncryptedData &&
  659. file.Metadata.DecryptionHeader == existing.Metadata.DecryptionHeader &&
  660. file.OwnerID == existing.OwnerID {
  661. // Already uploaded file
  662. file.ID = existing.ID
  663. return file, nil
  664. } else {
  665. // Overwrote an existing file or thumbnail
  666. go c.onExistingObjectsReplaced(file, hotDC)
  667. return ente.File{}, ente.ErrBadRequest
  668. }
  669. }
  670. func (c *FileController) onExistingObjectsReplaced(file ente.File, hotDC string) {
  671. defer func() {
  672. if r := recover(); r != nil {
  673. log.Errorf("Panic caught: %s, stack: %s", r, string(debug.Stack()))
  674. }
  675. }()
  676. log.Error("Replaced existing object, reverting", file)
  677. err := c.rollbackObject(file.File.ObjectKey)
  678. if err != nil {
  679. log.Error("Error rolling back latest file from hot storage", err)
  680. }
  681. err = c.rollbackObject(file.Thumbnail.ObjectKey)
  682. if err != nil {
  683. log.Error("Error rolling back latest thumbnail from hot storage", err)
  684. }
  685. c.FileRepo.ResetNeedsReplication(file, hotDC)
  686. }
  687. func (c *FileController) rollbackObject(objectKey string) error {
  688. versions, err := c.getVersions(objectKey)
  689. if err != nil {
  690. return stacktrace.Propagate(err, "")
  691. }
  692. if len(versions) > 1 {
  693. err = c.deleteObjectVersionFromHotStorage(objectKey,
  694. *versions[0].VersionId)
  695. if err != nil {
  696. return stacktrace.Propagate(err, "")
  697. }
  698. }
  699. return nil
  700. }
  701. func (c *FileController) getVersions(objectKey string) ([]*s3.ObjectVersion, error) {
  702. s3Client := c.S3Config.GetHotS3Client()
  703. response, err := s3Client.ListObjectVersions(&s3.ListObjectVersionsInput{
  704. Prefix: &objectKey,
  705. Bucket: c.S3Config.GetHotBucket(),
  706. })
  707. if err != nil {
  708. return nil, stacktrace.Propagate(err, "")
  709. }
  710. return response.Versions, nil
  711. }
  712. func (c *FileController) deleteObjectVersionFromHotStorage(objectKey string, versionID string) error {
  713. var s3Client = c.S3Config.GetHotS3Client()
  714. _, err := s3Client.DeleteObject(&s3.DeleteObjectInput{
  715. Bucket: c.S3Config.GetHotBucket(),
  716. Key: &objectKey,
  717. VersionId: &versionID,
  718. })
  719. if err != nil {
  720. return stacktrace.Propagate(err, "")
  721. }
  722. err = s3Client.WaitUntilObjectNotExists(&s3.HeadObjectInput{
  723. Bucket: c.S3Config.GetHotBucket(),
  724. Key: &objectKey,
  725. })
  726. if err != nil {
  727. return stacktrace.Propagate(err, "")
  728. }
  729. return nil
  730. }
  731. func (c *FileController) verifyFileAccess(actorUserID int64, fileID int64) error {
  732. fileOwnerID, err := c.FileRepo.GetOwnerID(fileID)
  733. if err != nil {
  734. return stacktrace.Propagate(err, "")
  735. }
  736. if fileOwnerID != actorUserID {
  737. cIDs, err := c.CollectionRepo.GetCollectionIDsSharedWithUser(actorUserID)
  738. if err != nil {
  739. return stacktrace.Propagate(err, "")
  740. }
  741. cwIDS, err := c.CollectionRepo.GetCollectionIDsSharedWithUser(fileOwnerID)
  742. if err != nil {
  743. return stacktrace.Propagate(err, "")
  744. }
  745. cIDs = append(cIDs, cwIDS...)
  746. accessible, err := c.CollectionRepo.DoesFileExistInCollections(fileID, cIDs)
  747. if err != nil {
  748. return stacktrace.Propagate(err, "")
  749. }
  750. if !accessible {
  751. return stacktrace.Propagate(ente.ErrPermissionDenied, "")
  752. }
  753. }
  754. return nil
  755. }
  756. func (c *FileController) getObjectURL(s3Client *s3.S3, dc string, bucket *string, objectKey string) (ente.UploadURL, error) {
  757. r, _ := s3Client.PutObjectRequest(&s3.PutObjectInput{
  758. Bucket: bucket,
  759. Key: &objectKey,
  760. })
  761. url, err := r.Presign(PreSignedRequestValidityDuration)
  762. if err != nil {
  763. return ente.UploadURL{}, stacktrace.Propagate(err, "")
  764. }
  765. err = c.ObjectCleanupCtrl.AddTempObjectKey(objectKey, dc)
  766. if err != nil {
  767. return ente.UploadURL{}, stacktrace.Propagate(err, "")
  768. }
  769. return ente.UploadURL{ObjectKey: objectKey, URL: url}, nil
  770. }
  771. // GetMultipartUploadURLs return collections of url to upload the parts of the files
  772. func (c *FileController) GetMultipartUploadURLs(ctx context.Context, userID int64, count int, app ente.App) (ente.MultipartUploadURLs, error) {
  773. err := c.UsageCtrl.CanUploadFile(ctx, userID, nil, app)
  774. if err != nil {
  775. return ente.MultipartUploadURLs{}, stacktrace.Propagate(err, "")
  776. }
  777. s3Client := c.S3Config.GetHotS3Client()
  778. dc := c.S3Config.GetHotDataCenter()
  779. bucket := c.S3Config.GetHotBucket()
  780. objectKey := strconv.FormatInt(userID, 10) + "/" + uuid.NewString()
  781. r, err := s3Client.CreateMultipartUpload(&s3.CreateMultipartUploadInput{
  782. Bucket: bucket,
  783. Key: &objectKey,
  784. })
  785. if err != nil {
  786. return ente.MultipartUploadURLs{}, stacktrace.Propagate(err, "")
  787. }
  788. err = c.ObjectCleanupCtrl.AddMultipartTempObjectKey(objectKey, *r.UploadId, dc)
  789. if err != nil {
  790. return ente.MultipartUploadURLs{}, stacktrace.Propagate(err, "")
  791. }
  792. multipartUploadURLs := ente.MultipartUploadURLs{ObjectKey: objectKey}
  793. urls := make([]string, 0)
  794. for i := 0; i < count; i++ {
  795. url, err := c.getPartURL(*s3Client, objectKey, int64(i+1), r.UploadId)
  796. if err != nil {
  797. return multipartUploadURLs, stacktrace.Propagate(err, "")
  798. }
  799. urls = append(urls, url)
  800. }
  801. multipartUploadURLs.PartURLs = urls
  802. r2, _ := s3Client.CompleteMultipartUploadRequest(&s3.CompleteMultipartUploadInput{
  803. Bucket: c.S3Config.GetHotBucket(),
  804. Key: &objectKey,
  805. UploadId: r.UploadId,
  806. })
  807. url, err := r2.Presign(PreSignedRequestValidityDuration)
  808. if err != nil {
  809. return multipartUploadURLs, stacktrace.Propagate(err, "")
  810. }
  811. multipartUploadURLs.CompleteURL = url
  812. return multipartUploadURLs, nil
  813. }
  814. func (c *FileController) getPartURL(s3Client s3.S3, objectKey string, partNumber int64, uploadID *string) (string, error) {
  815. r, _ := s3Client.UploadPartRequest(&s3.UploadPartInput{
  816. Bucket: c.S3Config.GetHotBucket(),
  817. Key: &objectKey,
  818. UploadId: uploadID,
  819. PartNumber: &partNumber,
  820. })
  821. url, err := r.Presign(PreSignedPartUploadRequestDuration)
  822. if err != nil {
  823. return "", stacktrace.Propagate(err, "")
  824. }
  825. return url, nil
  826. }