collection.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. package controller
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "github.com/ente-io/museum/pkg/repo/cast"
  7. "runtime/debug"
  8. "strings"
  9. "github.com/ente-io/museum/pkg/controller/access"
  10. "github.com/gin-contrib/requestid"
  11. "github.com/google/go-cmp/cmp"
  12. "github.com/google/go-cmp/cmp/cmpopts"
  13. "github.com/ente-io/museum/pkg/utils/array"
  14. "github.com/ente-io/museum/pkg/utils/auth"
  15. "github.com/gin-gonic/gin"
  16. "github.com/ente-io/museum/ente"
  17. "github.com/ente-io/museum/pkg/repo"
  18. "github.com/ente-io/museum/pkg/utils/time"
  19. "github.com/ente-io/stacktrace"
  20. log "github.com/sirupsen/logrus"
  21. )
  22. const (
  23. CollectionDiffLimit = 2500
  24. )
  25. // CollectionController encapsulates logic that deals with collections
  26. type CollectionController struct {
  27. PublicCollectionCtrl *PublicCollectionController
  28. AccessCtrl access.Controller
  29. BillingCtrl *BillingController
  30. CollectionRepo *repo.CollectionRepository
  31. UserRepo *repo.UserRepository
  32. FileRepo *repo.FileRepository
  33. QueueRepo *repo.QueueRepository
  34. CastRepo *cast.Repository
  35. TaskRepo *repo.TaskLockRepository
  36. }
  37. // Create creates a collection
  38. func (c *CollectionController) Create(collection ente.Collection, ownerID int64) (ente.Collection, error) {
  39. // The key attribute check is to ensure that user does not end up uploading any files before actually setting the key attributes.
  40. if _, keyErr := c.UserRepo.GetKeyAttributes(ownerID); keyErr != nil {
  41. return ente.Collection{}, stacktrace.Propagate(keyErr, "Unable to get keyAttributes")
  42. }
  43. collectionType := collection.Type
  44. collection.Owner.ID = ownerID
  45. collection.UpdationTime = time.Microseconds()
  46. // [20th Dec 2022] Patch on server side untill majority of the existing mobile clients upgrade to a version higher > 0.7.0
  47. // https://github.com/ente-io/photos-app/pull/725
  48. if collection.Type == "CollectionType.album" {
  49. collection.Type = "album"
  50. }
  51. if !array.StringInList(collection.Type, ente.ValidCollectionTypes) {
  52. return ente.Collection{}, stacktrace.Propagate(fmt.Errorf("unexpected collection type %s", collection.Type), "")
  53. }
  54. collection, err := c.CollectionRepo.Create(collection)
  55. if err != nil {
  56. if err == ente.ErrUncategorizeCollectionAlreadyExists || err == ente.ErrFavoriteCollectionAlreadyExist {
  57. dbCollection, err := c.CollectionRepo.GetCollectionByType(ownerID, collectionType)
  58. if err != nil {
  59. return ente.Collection{}, stacktrace.Propagate(err, "")
  60. }
  61. if dbCollection.IsDeleted {
  62. return ente.Collection{}, stacktrace.Propagate(fmt.Errorf("special collection of type : %s is deleted", collectionType), "")
  63. }
  64. return dbCollection, nil
  65. }
  66. return ente.Collection{}, stacktrace.Propagate(err, "")
  67. }
  68. return collection, nil
  69. }
  70. // GetOwned returns the list of collections owned by a user
  71. func (c *CollectionController) GetOwned(userID int64, sinceTime int64, app ente.App) ([]ente.Collection, error) {
  72. collections, err := c.CollectionRepo.GetCollectionsOwnedByUser(userID, sinceTime, app)
  73. if err != nil {
  74. return nil, stacktrace.Propagate(err, "")
  75. }
  76. go func() {
  77. defer func() {
  78. if r := recover(); r != nil {
  79. log.Errorf("Panic caught: %s, stack: %s", r, string(debug.Stack()))
  80. }
  81. }()
  82. collectionsV2, errV2 := c.CollectionRepo.GetCollectionsOwnedByUserV2(userID, sinceTime, app)
  83. if errV2 != nil {
  84. log.WithError(errV2).Error("failed to fetch collections using v2")
  85. }
  86. isEqual := cmp.Equal(collections, collectionsV2, cmpopts.SortSlices(func(a, b ente.Collection) bool { return a.ID < b.ID }))
  87. if !isEqual {
  88. jsonV1, _ := json.Marshal(collections)
  89. jsonV2, _ := json.Marshal(collectionsV2)
  90. log.WithFields(log.Fields{
  91. "v1": string(jsonV1),
  92. "v2": string(jsonV2),
  93. }).Error("collections diff didn't match")
  94. } else {
  95. log.Info("collections diff matched")
  96. }
  97. }()
  98. return collections, nil
  99. }
  100. // GetOwnedV2 returns the list of collections owned by a user using optimized query
  101. func (c *CollectionController) GetOwnedV2(userID int64, sinceTime int64, app ente.App) ([]ente.Collection, error) {
  102. collections, err := c.CollectionRepo.GetCollectionsOwnedByUserV2(userID, sinceTime, app)
  103. if err != nil {
  104. return nil, stacktrace.Propagate(err, "")
  105. }
  106. return collections, nil
  107. }
  108. // GetCollection returns the collection for given collectionID
  109. func (c *CollectionController) GetCollection(ctx *gin.Context, userID int64, cID int64) (ente.Collection, error) {
  110. resp, err := c.AccessCtrl.GetCollection(ctx, &access.GetCollectionParams{
  111. CollectionID: cID,
  112. ActorUserID: userID,
  113. IncludeDeleted: true,
  114. })
  115. if err != nil {
  116. return ente.Collection{}, stacktrace.Propagate(err, "")
  117. }
  118. return resp.Collection, nil
  119. }
  120. // GetSharedWith returns the list of collections that are shared with a user
  121. func (c *CollectionController) GetSharedWith(userID int64, sinceTime int64, app ente.App) ([]ente.Collection, error) {
  122. collections, err := c.CollectionRepo.GetCollectionsSharedWithUser(userID, sinceTime, app)
  123. if err != nil {
  124. return nil, stacktrace.Propagate(err, "")
  125. }
  126. return collections, nil
  127. }
  128. // Share shares a collection with a user
  129. func (c *CollectionController) Share(ctx *gin.Context, req ente.AlterShareRequest) ([]ente.CollectionUser, error) {
  130. fromUserID := auth.GetUserID(ctx.Request.Header)
  131. cID := req.CollectionID
  132. encryptedKey := req.EncryptedKey
  133. toUserEmail := strings.ToLower(strings.TrimSpace(req.Email))
  134. // default role type
  135. role := ente.VIEWER
  136. if req.Role != nil {
  137. role = *req.Role
  138. }
  139. toUserID, err := c.UserRepo.GetUserIDWithEmail(toUserEmail)
  140. if err != nil {
  141. return nil, stacktrace.Propagate(err, "")
  142. }
  143. if toUserID == fromUserID {
  144. return nil, stacktrace.Propagate(ente.ErrBadRequest, "Can not share collection with self")
  145. }
  146. collection, err := c.CollectionRepo.Get(cID)
  147. if err != nil {
  148. return nil, stacktrace.Propagate(err, "")
  149. }
  150. if !collection.AllowSharing() {
  151. return nil, stacktrace.Propagate(ente.ErrBadRequest, fmt.Sprintf("sharing %s is not allowed", collection.Type))
  152. }
  153. if fromUserID != collection.Owner.ID {
  154. return nil, stacktrace.Propagate(ente.ErrPermissionDenied, "")
  155. }
  156. err = c.BillingCtrl.HasActiveSelfOrFamilySubscription(fromUserID)
  157. if err != nil {
  158. return nil, stacktrace.Propagate(err, "")
  159. }
  160. err = c.CollectionRepo.Share(cID, fromUserID, toUserID, encryptedKey, role, time.Microseconds())
  161. if err != nil {
  162. return nil, stacktrace.Propagate(err, "")
  163. }
  164. sharees, err := c.GetSharees(ctx, cID, fromUserID)
  165. if err != nil {
  166. return nil, stacktrace.Propagate(err, "")
  167. }
  168. return sharees, nil
  169. }
  170. // UnShare unshares a collection with a user
  171. func (c *CollectionController) UnShare(ctx *gin.Context, cID int64, fromUserID int64, toUserEmail string) ([]ente.CollectionUser, error) {
  172. toUserID, err := c.UserRepo.GetUserIDWithEmail(toUserEmail)
  173. if err != nil {
  174. return nil, stacktrace.Propagate(ente.ErrNotFound, "")
  175. }
  176. collection, err := c.CollectionRepo.Get(cID)
  177. if err != nil {
  178. return nil, stacktrace.Propagate(err, "")
  179. }
  180. isLeavingCollection := toUserID == fromUserID
  181. if fromUserID != collection.Owner.ID || isLeavingCollection {
  182. return nil, stacktrace.Propagate(ente.ErrPermissionDenied, "")
  183. }
  184. err = c.CollectionRepo.UnShare(cID, toUserID)
  185. if err != nil {
  186. return nil, stacktrace.Propagate(err, "")
  187. }
  188. err = c.CastRepo.RevokeForGivenUserAndCollection(ctx, cID, toUserID)
  189. if err != nil {
  190. return nil, stacktrace.Propagate(err, "")
  191. }
  192. sharees, err := c.GetSharees(ctx, cID, fromUserID)
  193. if err != nil {
  194. return nil, stacktrace.Propagate(err, "")
  195. }
  196. return sharees, nil
  197. }
  198. // Leave leaves the collection owned by someone else,
  199. func (c *CollectionController) Leave(ctx *gin.Context, cID int64) error {
  200. userID := auth.GetUserID(ctx.Request.Header)
  201. collection, err := c.CollectionRepo.Get(cID)
  202. if err != nil {
  203. return stacktrace.Propagate(err, "")
  204. }
  205. if userID == collection.Owner.ID {
  206. return stacktrace.Propagate(ente.ErrPermissionDenied, "can not leave collection owned by self")
  207. }
  208. sharedCollectionIDs, err := c.CollectionRepo.GetCollectionIDsSharedWithUser(userID)
  209. if err != nil {
  210. return stacktrace.Propagate(err, "")
  211. }
  212. if !array.Int64InList(cID, sharedCollectionIDs) {
  213. return nil
  214. }
  215. err = c.CastRepo.RevokeForGivenUserAndCollection(ctx, cID, userID)
  216. if err != nil {
  217. return stacktrace.Propagate(err, "")
  218. }
  219. err = c.CollectionRepo.UnShare(cID, userID)
  220. if err != nil {
  221. return stacktrace.Propagate(err, "")
  222. }
  223. return nil
  224. }
  225. func (c *CollectionController) UpdateShareeMagicMetadata(ctx *gin.Context, req ente.UpdateCollectionMagicMetadata) error {
  226. actorUserId := auth.GetUserID(ctx.Request.Header)
  227. resp, err := c.AccessCtrl.GetCollection(ctx, &access.GetCollectionParams{
  228. CollectionID: req.ID,
  229. ActorUserID: actorUserId,
  230. })
  231. if err != nil {
  232. return stacktrace.Propagate(err, "")
  233. }
  234. if resp.Collection.Owner.ID == actorUserId {
  235. return stacktrace.Propagate(ente.NewBadRequestWithMessage("owner can not update sharee magic metadata"), "")
  236. }
  237. err = c.CollectionRepo.UpdateShareeMetadata(req.ID, resp.Collection.Owner.ID, actorUserId, req.MagicMetadata, time.Microseconds())
  238. if err != nil {
  239. return stacktrace.Propagate(err, "failed to update sharee magic metadata")
  240. }
  241. return nil
  242. }
  243. // ShareURL generates a public auth-token for the given collectionID
  244. func (c *CollectionController) ShareURL(ctx context.Context, userID int64, req ente.CreatePublicAccessTokenRequest) (
  245. ente.PublicURL, error) {
  246. collection, err := c.CollectionRepo.Get(req.CollectionID)
  247. if err != nil {
  248. return ente.PublicURL{}, stacktrace.Propagate(err, "")
  249. }
  250. if !collection.AllowSharing() {
  251. return ente.PublicURL{}, stacktrace.Propagate(ente.ErrBadRequest, fmt.Sprintf("sharing %s is not allowed", collection.Type))
  252. }
  253. if userID != collection.Owner.ID {
  254. return ente.PublicURL{}, stacktrace.Propagate(ente.ErrPermissionDenied, "")
  255. }
  256. err = c.BillingCtrl.HasActiveSelfOrFamilySubscription(userID)
  257. if err != nil {
  258. return ente.PublicURL{}, stacktrace.Propagate(err, "")
  259. }
  260. response, err := c.PublicCollectionCtrl.CreateAccessToken(ctx, req)
  261. if err != nil {
  262. return ente.PublicURL{}, stacktrace.Propagate(err, "")
  263. }
  264. return response, nil
  265. }
  266. // UpdateShareURL updates the shared url configuration
  267. func (c *CollectionController) UpdateShareURL(ctx context.Context, userID int64, req ente.UpdatePublicAccessTokenRequest) (
  268. ente.PublicURL, error) {
  269. if err := c.verifyOwnership(req.CollectionID, userID); err != nil {
  270. return ente.PublicURL{}, stacktrace.Propagate(err, "")
  271. }
  272. err := c.BillingCtrl.HasActiveSelfOrFamilySubscription(userID)
  273. if err != nil {
  274. return ente.PublicURL{}, stacktrace.Propagate(err, "")
  275. }
  276. response, err := c.PublicCollectionCtrl.UpdateSharedUrl(ctx, req)
  277. if err != nil {
  278. return ente.PublicURL{}, stacktrace.Propagate(err, "")
  279. }
  280. return response, nil
  281. }
  282. // DisableSharedURL disable a public auth-token for the given collectionID
  283. func (c *CollectionController) DisableSharedURL(ctx context.Context, userID int64, cID int64) error {
  284. if err := c.verifyOwnership(cID, userID); err != nil {
  285. return stacktrace.Propagate(err, "")
  286. }
  287. err := c.PublicCollectionCtrl.Disable(ctx, cID)
  288. return stacktrace.Propagate(err, "")
  289. }
  290. // AddFiles adds files to a collection
  291. func (c *CollectionController) AddFiles(ctx *gin.Context, userID int64, files []ente.CollectionFileItem, cID int64) error {
  292. resp, err := c.AccessCtrl.GetCollection(ctx, &access.GetCollectionParams{
  293. CollectionID: cID,
  294. ActorUserID: userID,
  295. IncludeDeleted: false,
  296. })
  297. if err != nil {
  298. return stacktrace.Propagate(err, "failed to verify collection access")
  299. }
  300. if !resp.Role.CanAdd() {
  301. return stacktrace.Propagate(ente.ErrPermissionDenied, fmt.Sprintf("user %d with role %s can not add files", userID, *resp.Role))
  302. }
  303. collectionOwnerID := resp.Collection.Owner.ID
  304. filesOwnerID := userID
  305. // Verify that the user owns each file
  306. fileIDs := make([]int64, 0)
  307. for _, file := range files {
  308. fileIDs = append(fileIDs, file.ID)
  309. }
  310. err = c.AccessCtrl.VerifyFileOwnership(ctx, &access.VerifyFileOwnershipParams{
  311. ActorUserId: userID,
  312. FileIDs: fileIDs,
  313. })
  314. if err != nil {
  315. return stacktrace.Propagate(err, "Failed to verify fileOwnership")
  316. }
  317. err = c.CollectionRepo.AddFiles(cID, collectionOwnerID, files, filesOwnerID)
  318. if err != nil {
  319. return stacktrace.Propagate(err, "")
  320. }
  321. return nil
  322. }
  323. // RestoreFiles restore files from trash and add to the collection
  324. func (c *CollectionController) RestoreFiles(ctx *gin.Context, userID int64, cID int64, files []ente.CollectionFileItem) error {
  325. _, err := c.AccessCtrl.GetCollection(ctx, &access.GetCollectionParams{
  326. CollectionID: cID,
  327. ActorUserID: userID,
  328. IncludeDeleted: false,
  329. VerifyOwner: true,
  330. })
  331. if err != nil {
  332. return stacktrace.Propagate(err, "failed to verify collection access")
  333. }
  334. // Verify that the user owns each file
  335. for _, file := range files {
  336. // todo #perf find owners of all files
  337. ownerID, err := c.FileRepo.GetOwnerID(file.ID)
  338. if err != nil {
  339. return stacktrace.Propagate(err, "")
  340. }
  341. if ownerID != userID {
  342. log.WithFields(log.Fields{
  343. "file_id": file.ID,
  344. "owner_id": ownerID,
  345. "user_id": userID,
  346. }).Error("invalid ops: can't add file which isn't owned by user")
  347. return stacktrace.Propagate(ente.ErrPermissionDenied, "")
  348. }
  349. }
  350. err = c.CollectionRepo.RestoreFiles(ctx, userID, cID, files)
  351. if err != nil {
  352. return stacktrace.Propagate(err, "")
  353. }
  354. return nil
  355. }
  356. // MoveFiles from one collection to another collection. Both the collections and files should belong to
  357. // single user
  358. func (c *CollectionController) MoveFiles(ctx *gin.Context, req ente.MoveFilesRequest) error {
  359. userID := auth.GetUserID(ctx.Request.Header)
  360. _, err := c.AccessCtrl.GetCollection(ctx, &access.GetCollectionParams{
  361. CollectionID: req.FromCollectionID,
  362. ActorUserID: userID,
  363. IncludeDeleted: false,
  364. VerifyOwner: true,
  365. })
  366. if err != nil {
  367. return stacktrace.Propagate(err, "failed to verify if actor owns fromCollection")
  368. }
  369. _, err = c.AccessCtrl.GetCollection(ctx, &access.GetCollectionParams{
  370. CollectionID: req.ToCollectionID,
  371. ActorUserID: userID,
  372. IncludeDeleted: false,
  373. VerifyOwner: true,
  374. })
  375. if err != nil {
  376. return stacktrace.Propagate(err, "failed to verify if actor owns toCollection")
  377. }
  378. // Verify that the user owns each file
  379. fileIDs := make([]int64, 0)
  380. for _, file := range req.Files {
  381. fileIDs = append(fileIDs, file.ID)
  382. }
  383. err = c.AccessCtrl.VerifyFileOwnership(ctx, &access.VerifyFileOwnershipParams{
  384. ActorUserId: userID,
  385. FileIDs: fileIDs,
  386. })
  387. if err != nil {
  388. stacktrace.Propagate(err, "Failed to verify fileOwnership")
  389. }
  390. err = c.CollectionRepo.MoveFiles(ctx, req.ToCollectionID, req.FromCollectionID, req.Files, userID, userID)
  391. return stacktrace.Propagate(err, "") // return nil if err is nil
  392. }
  393. // RemoveFilesV3 removes files from a collection as long as owner(s) of the file is different from collection owner
  394. func (c *CollectionController) RemoveFilesV3(ctx *gin.Context, req ente.RemoveFilesV3Request) error {
  395. actorUserID := auth.GetUserID(ctx.Request.Header)
  396. resp, err := c.AccessCtrl.GetCollection(ctx, &access.GetCollectionParams{
  397. CollectionID: req.CollectionID,
  398. ActorUserID: actorUserID,
  399. VerifyOwner: false,
  400. })
  401. if err != nil {
  402. return stacktrace.Propagate(err, "failed to verify collection access")
  403. }
  404. err = c.isRemoveAllowed(ctx, actorUserID, resp.Collection.Owner.ID, req.FileIDs)
  405. if err != nil {
  406. return stacktrace.Propagate(err, "file removal check failed")
  407. }
  408. err = c.CollectionRepo.RemoveFilesV3(ctx, req.CollectionID, req.FileIDs)
  409. if err != nil {
  410. return stacktrace.Propagate(err, "failed to remove files")
  411. }
  412. return nil
  413. }
  414. // isRemoveAllowed verifies that given set of files can be removed from the collection or not
  415. func (c *CollectionController) isRemoveAllowed(ctx *gin.Context, actorUserID int64, collectionOwnerID int64, fileIDs []int64) error {
  416. ownerToFilesMap, err := c.FileRepo.GetOwnerToFileIDsMap(ctx, fileIDs)
  417. if err != nil {
  418. return stacktrace.Propagate(err, "failed to get owner to fileIDs map")
  419. }
  420. // verify that none of the file belongs to the collection owner
  421. if _, ok := ownerToFilesMap[collectionOwnerID]; ok {
  422. return ente.NewBadRequestWithMessage("can not remove files owned by album owner")
  423. }
  424. if collectionOwnerID != actorUserID {
  425. // verify that user is only trying to remove files owned by them
  426. if len(ownerToFilesMap) > 1 {
  427. return stacktrace.Propagate(ente.ErrPermissionDenied, "can not remove files owned by others")
  428. }
  429. // verify that user is only trying to remove files owned by them
  430. if _, ok := ownerToFilesMap[actorUserID]; !ok {
  431. return stacktrace.Propagate(ente.ErrPermissionDenied, "can not remove files owned by others")
  432. }
  433. }
  434. return nil
  435. }
  436. func (c *CollectionController) isCopyAllowed(ctx *gin.Context, actorUserID int64, req ente.CopyFileSyncRequest) error {
  437. // verify that srcCollectionID is accessible by actorUserID
  438. if _, err := c.AccessCtrl.GetCollection(ctx, &access.GetCollectionParams{
  439. CollectionID: req.SrcCollectionID,
  440. ActorUserID: actorUserID,
  441. }); err != nil {
  442. return stacktrace.Propagate(err, "failed to verify srcCollection access")
  443. }
  444. // verify that dstCollectionID is owned by actorUserID
  445. if _, err := c.AccessCtrl.GetCollection(ctx, &access.GetCollectionParams{
  446. CollectionID: req.DstCollection,
  447. ActorUserID: actorUserID,
  448. VerifyOwner: true,
  449. }); err != nil {
  450. return stacktrace.Propagate(err, "failed to ownership of the dstCollection access")
  451. }
  452. // verify that all FileIDs exists in the srcCollection
  453. fileIDs := make([]int64, len(req.Files))
  454. for idx, file := range req.Files {
  455. fileIDs[idx] = file.ID
  456. }
  457. if err := c.CollectionRepo.VerifyAllFileIDsExistsInCollection(ctx, req.SrcCollectionID, fileIDs); err != nil {
  458. return stacktrace.Propagate(err, "failed to verify fileIDs in srcCollection")
  459. }
  460. dsMap, err := c.FileRepo.GetOwnerToFileIDsMap(ctx, fileIDs)
  461. if err != nil {
  462. return err
  463. }
  464. // verify that none of the file belongs to actorUserID
  465. if _, ok := dsMap[actorUserID]; ok {
  466. return ente.NewBadRequestWithMessage("can not copy files owned by actor")
  467. }
  468. return nil
  469. }
  470. // GetDiffV2 returns the changes in user's collections since a timestamp, along with hasMore bool flag.
  471. func (c *CollectionController) GetDiffV2(ctx *gin.Context, cID int64, userID int64, sinceTime int64) ([]ente.File, bool, error) {
  472. reqContextLogger := log.WithFields(log.Fields{
  473. "user_id": userID,
  474. "collection_id": cID,
  475. "since_time": sinceTime,
  476. "req_id": requestid.Get(ctx),
  477. })
  478. _, err := c.AccessCtrl.GetCollection(ctx, &access.GetCollectionParams{
  479. CollectionID: cID,
  480. ActorUserID: userID,
  481. })
  482. if err != nil {
  483. return nil, false, stacktrace.Propagate(err, "failed to verify access")
  484. }
  485. diff, hasMore, err := c.getDiff(cID, sinceTime, CollectionDiffLimit, reqContextLogger)
  486. if err != nil {
  487. return nil, false, stacktrace.Propagate(err, "")
  488. }
  489. // hide private metadata before returning files info in diff
  490. for idx := range diff {
  491. if diff[idx].OwnerID != userID {
  492. diff[idx].MagicMetadata = nil
  493. }
  494. if diff[idx].Metadata.EncryptedData == "-" && !diff[idx].IsDeleted {
  495. // This indicates that the file is deleted, but we still have a stale entry in the collection
  496. log.WithFields(log.Fields{
  497. "file_id": diff[idx].ID,
  498. "collection_id": cID,
  499. "updated_at": diff[idx].UpdationTime,
  500. }).Warning("stale collection_file found")
  501. diff[idx].IsDeleted = true
  502. }
  503. }
  504. return diff, hasMore, nil
  505. }
  506. func (c *CollectionController) GetFile(ctx *gin.Context, collectionID int64, fileID int64) (*ente.File, error) {
  507. userID := auth.GetUserID(ctx.Request.Header)
  508. files, err := c.CollectionRepo.GetFile(collectionID, fileID)
  509. if err != nil {
  510. return nil, stacktrace.Propagate(err, "")
  511. }
  512. if len(files) == 0 {
  513. return nil, stacktrace.Propagate(&ente.ErrFileNotFoundInAlbum, "")
  514. }
  515. file := files[0]
  516. if file.OwnerID != userID {
  517. cIDs, err := c.CollectionRepo.GetCollectionIDsSharedWithUser(userID)
  518. if err != nil {
  519. return nil, stacktrace.Propagate(err, "")
  520. }
  521. if !array.Int64InList(collectionID, cIDs) {
  522. return nil, stacktrace.Propagate(ente.ErrPermissionDenied, "")
  523. }
  524. }
  525. if file.IsDeleted {
  526. return nil, stacktrace.Propagate(&ente.ErrFileNotFoundInAlbum, "")
  527. }
  528. return &file, nil
  529. }
  530. // GetPublicDiff returns the changes in the collections since a timestamp, along with hasMore bool flag.
  531. func (c *CollectionController) GetPublicDiff(ctx *gin.Context, sinceTime int64) ([]ente.File, bool, error) {
  532. accessContext := auth.MustGetPublicAccessContext(ctx)
  533. reqContextLogger := log.WithFields(log.Fields{
  534. "public_id": accessContext.ID,
  535. "collection_id": accessContext.CollectionID,
  536. "since_time": sinceTime,
  537. "req_id": requestid.Get(ctx),
  538. })
  539. diff, hasMore, err := c.getDiff(accessContext.CollectionID, sinceTime, CollectionDiffLimit, reqContextLogger)
  540. if err != nil {
  541. return nil, false, stacktrace.Propagate(err, "")
  542. }
  543. // hide private metadata before returning files info in diff
  544. for idx := range diff {
  545. if diff[idx].MagicMetadata != nil {
  546. diff[idx].MagicMetadata = nil
  547. }
  548. }
  549. return diff, hasMore, nil
  550. }
  551. // getDiff returns the diff in user's collection since a timestamp, along with hasMore bool flag.
  552. // The function will never return partial result for a version. To maintain this promise, it will not be able to honor
  553. // the limit parameter. Based on the db state, compared to the limit, the diff length can be
  554. // less (case 1), more (case 2), or same (case 3, 4)
  555. // Example: Assume we have 11 files with following versions: v0, v1, v1, v1, v1, v1, v1, v1, v2, v2, v2 (count = 7 v1, 3 v2)
  556. // client has synced up till version v0.
  557. // case 1: ( sinceTime: v0, limit = 8):
  558. // The method will discard the entries with version v2 and return only 7 entries with version v1.
  559. // case 2: (sinceTime: v0, limit 5):
  560. // Instead of returning 5 entries with version V1, method will return all 7 entries with version v1.
  561. // case 3: (sinceTime: v0, limit 7):
  562. // The method will return all 7 entries with version V1.
  563. // case 4: (sinceTime: v0, limit >=10):
  564. // The method will all 10 entries in the diff
  565. func (c *CollectionController) getDiff(cID int64, sinceTime int64, limit int, logger *log.Entry) ([]ente.File, bool, error) {
  566. // request for limit +1 files
  567. diffLimitPlusOne, err := c.CollectionRepo.GetDiff(cID, sinceTime, limit+1)
  568. if err != nil {
  569. return nil, false, stacktrace.Propagate(err, "")
  570. }
  571. if len(diffLimitPlusOne) <= limit {
  572. // case 4: all files changed after sinceTime are included.
  573. return diffLimitPlusOne, false, nil
  574. }
  575. lastFileVersion := diffLimitPlusOne[limit].UpdationTime
  576. filteredDiffs := c.removeFilesWithVersion(diffLimitPlusOne, lastFileVersion)
  577. filteredDiffLen := len(filteredDiffs)
  578. if filteredDiffLen > 0 { // case 1 or case 3
  579. if filteredDiffLen < limit {
  580. // logging case 1
  581. logger.
  582. WithField("last_file_version", lastFileVersion).
  583. WithField("filtered_diff_len", filteredDiffLen).
  584. Info(fmt.Sprintf("less than limit (%d) files in diff", limit))
  585. }
  586. return filteredDiffs, true, nil
  587. }
  588. // case 2
  589. diff, err := c.CollectionRepo.GetFilesWithVersion(cID, lastFileVersion)
  590. logger.
  591. WithField("last_file_version", lastFileVersion).
  592. WithField("count", len(diff)).
  593. Info(fmt.Sprintf("more than limit (%d) files with same version", limit))
  594. if err != nil {
  595. return nil, false, stacktrace.Propagate(err, "")
  596. }
  597. return diff, true, nil
  598. }
  599. // removeFilesWithVersion returns filtered list of files are removing all files with given version.
  600. // Important: The method assumes that files are sorted by increasing order of File.UpdationTime
  601. func (c *CollectionController) removeFilesWithVersion(files []ente.File, version int64) []ente.File {
  602. var i = len(files) - 1
  603. for ; i >= 0; i-- {
  604. if files[i].UpdationTime != version {
  605. // found index (from end) where file's version is different from given version
  606. break
  607. }
  608. }
  609. return files[0 : i+1]
  610. }
  611. // GetSharees returns the list of users a collection has been shared with
  612. func (c *CollectionController) GetSharees(ctx *gin.Context, cID int64, userID int64) ([]ente.CollectionUser, error) {
  613. _, err := c.AccessCtrl.GetCollection(ctx, &access.GetCollectionParams{
  614. CollectionID: cID,
  615. ActorUserID: userID,
  616. })
  617. if err != nil {
  618. return nil, stacktrace.Propagate(err, "Access check failed")
  619. }
  620. sharees, err := c.CollectionRepo.GetSharees(cID)
  621. if err != nil {
  622. return nil, stacktrace.Propagate(err, "")
  623. }
  624. return sharees, nil
  625. }
  626. // TrashV3 deletes a given collection and based on user input (TrashCollectionV3Request.KeepFiles as FALSE) , it will move all files present in the underlying collection
  627. // to trash.
  628. func (c *CollectionController) TrashV3(ctx *gin.Context, req ente.TrashCollectionV3Request) error {
  629. if req.KeepFiles == nil {
  630. return ente.ErrBadRequest
  631. }
  632. userID := auth.GetUserID(ctx.Request.Header)
  633. cID := req.CollectionID
  634. resp, err := c.AccessCtrl.GetCollection(ctx, &access.GetCollectionParams{
  635. CollectionID: cID,
  636. ActorUserID: userID,
  637. IncludeDeleted: true,
  638. VerifyOwner: true,
  639. })
  640. if err != nil {
  641. return stacktrace.Propagate(err, "")
  642. }
  643. if !resp.Collection.AllowDelete() {
  644. return stacktrace.Propagate(ente.ErrBadRequest, fmt.Sprintf("deleting albums of type %s is not allowed", resp.Collection.Type))
  645. }
  646. if resp.Collection.IsDeleted {
  647. log.WithFields(log.Fields{
  648. "c_id": cID,
  649. "user_id": userID,
  650. }).Warning("Collection is already deleted")
  651. return nil
  652. }
  653. if *req.KeepFiles {
  654. // Verify that all files from this particular collections have been removed.
  655. count, err := c.CollectionRepo.GetCollectionsFilesCount(cID)
  656. if err != nil {
  657. return stacktrace.Propagate(err, "")
  658. }
  659. if count != 0 {
  660. return stacktrace.Propagate(&ente.ErrCollectionNotEmpty, fmt.Sprintf("Collection file count %d", count))
  661. }
  662. }
  663. err = c.PublicCollectionCtrl.Disable(ctx, cID)
  664. if err != nil {
  665. return stacktrace.Propagate(err, "failed to disabled public share url")
  666. }
  667. err = c.CastRepo.RevokeTokenForCollection(ctx, cID)
  668. if err != nil {
  669. return stacktrace.Propagate(err, "failed to revoke cast token")
  670. }
  671. // Continue with current delete flow till. This disables sharing for this collection and then queue it up for deletion
  672. err = c.CollectionRepo.ScheduleDelete(cID)
  673. if err != nil {
  674. return stacktrace.Propagate(err, "")
  675. }
  676. return nil
  677. }
  678. // Rename updates the collection's name
  679. func (c *CollectionController) Rename(userID int64, cID int64, encryptedName string, nameDecryptionNonce string) error {
  680. if err := c.verifyOwnership(cID, userID); err != nil {
  681. return stacktrace.Propagate(err, "")
  682. }
  683. err := c.CollectionRepo.Rename(cID, encryptedName, nameDecryptionNonce)
  684. if err != nil {
  685. return stacktrace.Propagate(err, "")
  686. }
  687. return nil
  688. }
  689. // UpdateMagicMetadata updates the magic metadata for given collection
  690. func (c *CollectionController) UpdateMagicMetadata(ctx *gin.Context, request ente.UpdateCollectionMagicMetadata, isPublicMetadata bool) error {
  691. userID := auth.GetUserID(ctx.Request.Header)
  692. if err := c.verifyOwnership(request.ID, userID); err != nil {
  693. return stacktrace.Propagate(err, "")
  694. }
  695. // todo: verify version mismatch later. We are not planning to resync collection on clients,
  696. // so ignore that check until then. Ideally, after file size info sync, we should enable
  697. err := c.CollectionRepo.UpdateMagicMetadata(ctx, request.ID, request.MagicMetadata, isPublicMetadata)
  698. if err != nil {
  699. return stacktrace.Propagate(err, "")
  700. }
  701. return nil
  702. }
  703. func (c *CollectionController) HandleAccountDeletion(ctx context.Context, userID int64, logger *log.Entry) error {
  704. logger.Info("disabling shared collections with or by the user")
  705. sharedCollections, err := c.CollectionRepo.GetAllSharedCollections(ctx, userID)
  706. if err != nil {
  707. return stacktrace.Propagate(err, "")
  708. }
  709. logger.Info(fmt.Sprintf("shared collections count: %d", len(sharedCollections)))
  710. for _, shareCollection := range sharedCollections {
  711. logger.WithField("shared_collection", shareCollection).Info("disable shared collection")
  712. err = c.CollectionRepo.UnShare(shareCollection.CollectionID, shareCollection.ToUserID)
  713. if err != nil {
  714. return stacktrace.Propagate(err, "")
  715. }
  716. }
  717. err = c.CastRepo.RevokeTokenForUser(ctx, userID)
  718. if err != nil {
  719. return stacktrace.Propagate(err, "failed to revoke cast token for user")
  720. }
  721. err = c.PublicCollectionCtrl.HandleAccountDeletion(ctx, userID, logger)
  722. return stacktrace.Propagate(err, "")
  723. }
  724. // Verify that user owns the collection
  725. func (c *CollectionController) verifyOwnership(cID int64, userID int64) error {
  726. collection, err := c.CollectionRepo.Get(cID)
  727. if err != nil {
  728. return stacktrace.Propagate(err, "")
  729. }
  730. if userID != collection.Owner.ID {
  731. return stacktrace.Propagate(ente.ErrPermissionDenied, "")
  732. }
  733. return nil
  734. }