file.go 31 KB

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