OwnershipTransferHandler.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. const logger = require('@overleaf/logger')
  2. const { Project } = require('../../models/Project')
  3. const ProjectGetter = require('../Project/ProjectGetter')
  4. const UserGetter = require('../User/UserGetter')
  5. const CollaboratorsHandler = require('./CollaboratorsHandler')
  6. const EmailHandler = require('../Email/EmailHandler')
  7. const Errors = require('../Errors/Errors')
  8. const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
  9. const TpdsProjectFlusher = require('../ThirdPartyDataStore/TpdsProjectFlusher')
  10. const ProjectAuditLogHandler = require('../Project/ProjectAuditLogHandler')
  11. const AnalyticsManager = require('../Analytics/AnalyticsManager')
  12. module.exports = {
  13. promises: { transferOwnership },
  14. }
  15. async function transferOwnership(projectId, newOwnerId, options = {}) {
  16. const {
  17. allowTransferToNonCollaborators,
  18. sessionUserId,
  19. skipEmails,
  20. ipAddress,
  21. } = options
  22. // Fetch project and user
  23. const [project, newOwner] = await Promise.all([
  24. _getProject(projectId),
  25. _getUser(newOwnerId),
  26. ])
  27. // Exit early if the transferee is already the project owner
  28. const previousOwnerId = project.owner_ref
  29. if (previousOwnerId.equals(newOwnerId)) {
  30. return
  31. }
  32. // Check that user is already a collaborator
  33. if (
  34. !allowTransferToNonCollaborators &&
  35. !_userIsCollaborator(newOwner, project)
  36. ) {
  37. throw new Errors.UserNotCollaboratorError({ info: { userId: newOwnerId } })
  38. }
  39. // Track the change of ownership in BigQuery.
  40. AnalyticsManager.recordEventForUserInBackground(
  41. previousOwnerId,
  42. 'project-ownership-transfer',
  43. { projectId, newOwnerId }
  44. )
  45. // Transfer ownership
  46. await ProjectAuditLogHandler.promises.addEntry(
  47. projectId,
  48. 'transfer-ownership',
  49. sessionUserId,
  50. ipAddress,
  51. { previousOwnerId, newOwnerId }
  52. )
  53. // Determine which permissions to give old owner based on
  54. // new owner's existing permissions
  55. const newPermissions =
  56. _getUserPermissions(newOwner, project) || PrivilegeLevels.READ_ONLY
  57. await _transferOwnership(
  58. projectId,
  59. previousOwnerId,
  60. newOwnerId,
  61. newPermissions
  62. )
  63. // Flush project to TPDS
  64. await TpdsProjectFlusher.promises.flushProjectToTpds(projectId)
  65. // Send confirmation emails
  66. const previousOwner = await UserGetter.promises.getUser(previousOwnerId)
  67. if (!skipEmails) {
  68. await _sendEmails(project, previousOwner, newOwner)
  69. }
  70. }
  71. async function _getProject(projectId) {
  72. const project = await ProjectGetter.promises.getProject(projectId, {
  73. owner_ref: 1,
  74. collaberator_refs: 1,
  75. readOnly_refs: 1,
  76. name: 1,
  77. })
  78. if (project == null) {
  79. throw new Errors.ProjectNotFoundError({ info: { projectId } })
  80. }
  81. return project
  82. }
  83. async function _getUser(userId) {
  84. const user = await UserGetter.promises.getUser(userId)
  85. if (user == null) {
  86. throw new Errors.UserNotFoundError({ info: { userId } })
  87. }
  88. return user
  89. }
  90. function _getUserPermissions(user, project) {
  91. const collaboratorIds = project.collaberator_refs || []
  92. const readOnlyIds = project.readOnly_refs || []
  93. if (collaboratorIds.some(collaboratorId => collaboratorId.equals(user._id))) {
  94. return PrivilegeLevels.READ_AND_WRITE
  95. } else if (
  96. readOnlyIds.some(collaboratorId => collaboratorId.equals(user._id))
  97. ) {
  98. return PrivilegeLevels.READ_ONLY
  99. }
  100. }
  101. function _userIsCollaborator(user, project) {
  102. return Boolean(_getUserPermissions(user, project))
  103. }
  104. async function _transferOwnership(
  105. projectId,
  106. previousOwnerId,
  107. newOwnerId,
  108. newPermissions
  109. ) {
  110. await CollaboratorsHandler.promises.removeUserFromProject(
  111. projectId,
  112. newOwnerId
  113. )
  114. await Project.updateOne(
  115. { _id: projectId },
  116. { $set: { owner_ref: newOwnerId } }
  117. ).exec()
  118. await CollaboratorsHandler.promises.addUserIdToProject(
  119. projectId,
  120. newOwnerId,
  121. previousOwnerId,
  122. newPermissions
  123. )
  124. }
  125. async function _sendEmails(project, previousOwner, newOwner) {
  126. if (previousOwner == null) {
  127. // The previous owner didn't exist. This is not supposed to happen, but
  128. // since we're changing the owner anyway, we'll just warn
  129. logger.warn(
  130. { projectId: project._id, ownerId: previousOwner._id },
  131. 'Project owner did not exist before ownership transfer'
  132. )
  133. } else {
  134. // Send confirmation emails
  135. await Promise.all([
  136. EmailHandler.promises.sendEmail(
  137. 'ownershipTransferConfirmationPreviousOwner',
  138. {
  139. to: previousOwner.email,
  140. project,
  141. newOwner,
  142. }
  143. ),
  144. EmailHandler.promises.sendEmail('ownershipTransferConfirmationNewOwner', {
  145. to: newOwner.email,
  146. project,
  147. previousOwner,
  148. }),
  149. ])
  150. }
  151. }