AuthorizationManagerTests.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. const sinon = require('sinon')
  2. const { expect } = require('chai')
  3. const modulePath =
  4. '../../../../app/src/Features/Authorization/AuthorizationManager.js'
  5. const SandboxedModule = require('sandboxed-module')
  6. const Errors = require('../../../../app/src/Features/Errors/Errors')
  7. const PrivilegeLevels = require('../../../../app/src/Features/Authorization/PrivilegeLevels')
  8. const PublicAccessLevels = require('../../../../app/src/Features/Authorization/PublicAccessLevels')
  9. const { ObjectId } = require('mongodb')
  10. describe('AuthorizationManager', function () {
  11. beforeEach(function () {
  12. this.user = { _id: new ObjectId() }
  13. this.project = { _id: new ObjectId() }
  14. this.token = 'some-token'
  15. this.ProjectGetter = {
  16. promises: {
  17. getProject: sinon.stub().resolves(null),
  18. },
  19. }
  20. this.ProjectGetter.promises.getProject
  21. .withArgs(this.project._id)
  22. .resolves(this.project)
  23. this.CollaboratorsGetter = {
  24. promises: {
  25. getMemberIdPrivilegeLevel: sinon.stub().resolves(PrivilegeLevels.NONE),
  26. },
  27. }
  28. this.CollaboratorsHandler = {}
  29. this.User = {
  30. findOne: sinon.stub().returns({ exec: sinon.stub().resolves(null) }),
  31. }
  32. this.User.findOne
  33. .withArgs({ _id: this.user._id })
  34. .returns({ exec: sinon.stub().resolves(this.user) })
  35. this.TokenAccessHandler = {
  36. promises: {
  37. validateTokenForAnonymousAccess: sinon
  38. .stub()
  39. .resolves({ isValidReadAndWrite: false, isValidReadOnly: false }),
  40. },
  41. }
  42. this.AuthorizationManager = SandboxedModule.require(modulePath, {
  43. requires: {
  44. mongodb: { ObjectId },
  45. '../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
  46. '../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler,
  47. '../Project/ProjectGetter': this.ProjectGetter,
  48. '../../models/User': { User: this.User },
  49. '../TokenAccess/TokenAccessHandler': this.TokenAccessHandler,
  50. '@overleaf/settings': {
  51. passwordStrengthOptions: {},
  52. adminPrivilegeAvailable: true,
  53. },
  54. },
  55. })
  56. })
  57. describe('isRestrictedUser', function () {
  58. it('should produce the correct values', function () {
  59. const notRestrictedScenarios = [
  60. [null, 'readAndWrite', false, false],
  61. ['id', 'readAndWrite', true, false],
  62. ['id', 'readAndWrite', true, true],
  63. ['id', 'readOnly', false, false],
  64. ['id', 'readOnly', false, true],
  65. ]
  66. const restrictedScenarios = [
  67. [null, 'readOnly', false, false],
  68. ['id', 'readOnly', true, false],
  69. [null, false, true, false],
  70. [null, false, false, false],
  71. ['id', false, true, false],
  72. ['id', false, false, false],
  73. ]
  74. for (const notRestrictedArgs of notRestrictedScenarios) {
  75. expect(
  76. this.AuthorizationManager.isRestrictedUser(...notRestrictedArgs)
  77. ).to.equal(false)
  78. }
  79. for (const restrictedArgs of restrictedScenarios) {
  80. expect(
  81. this.AuthorizationManager.isRestrictedUser(...restrictedArgs)
  82. ).to.equal(true)
  83. }
  84. })
  85. })
  86. describe('getPrivilegeLevelForProject', function () {
  87. describe('with a token-based project', function () {
  88. beforeEach(function () {
  89. this.project.publicAccesLevel = 'tokenBased'
  90. })
  91. describe('with a user id with a privilege level', function () {
  92. beforeEach(async function () {
  93. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel
  94. .withArgs(this.user._id, this.project._id)
  95. .resolves(PrivilegeLevels.READ_ONLY)
  96. this.result =
  97. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  98. this.user._id,
  99. this.project._id,
  100. this.token
  101. )
  102. })
  103. it("should return the user's privilege level", function () {
  104. expect(this.result).to.equal('readOnly')
  105. })
  106. })
  107. describe('with a user id with no privilege level', function () {
  108. beforeEach(async function () {
  109. this.result =
  110. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  111. this.user._id,
  112. this.project._id,
  113. this.token
  114. )
  115. })
  116. it('should return false', function () {
  117. expect(this.result).to.equal(false)
  118. })
  119. })
  120. describe('with a user id who is an admin', function () {
  121. beforeEach(async function () {
  122. this.user.isAdmin = true
  123. this.result =
  124. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  125. this.user._id,
  126. this.project._id,
  127. this.token
  128. )
  129. })
  130. it('should return the user as an owner', function () {
  131. expect(this.result).to.equal('owner')
  132. })
  133. })
  134. describe('with no user (anonymous)', function () {
  135. describe('when the token is not valid', function () {
  136. beforeEach(async function () {
  137. this.result =
  138. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  139. null,
  140. this.project._id,
  141. this.token
  142. )
  143. })
  144. it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function () {
  145. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.called.should.equal(
  146. false
  147. )
  148. })
  149. it('should check if the token is valid', function () {
  150. this.TokenAccessHandler.promises.validateTokenForAnonymousAccess.should.have.been.calledWith(
  151. this.project._id,
  152. this.token
  153. )
  154. })
  155. it('should return false', function () {
  156. expect(this.result).to.equal(false)
  157. })
  158. })
  159. describe('when the token is valid for read-and-write', function () {
  160. beforeEach(async function () {
  161. this.TokenAccessHandler.promises.validateTokenForAnonymousAccess =
  162. sinon
  163. .stub()
  164. .withArgs(this.project._id, this.token)
  165. .resolves({ isValidReadAndWrite: true, isValidReadOnly: false })
  166. this.result =
  167. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  168. null,
  169. this.project._id,
  170. this.token
  171. )
  172. })
  173. it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function () {
  174. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.called.should.equal(
  175. false
  176. )
  177. })
  178. it('should check if the token is valid', function () {
  179. this.TokenAccessHandler.promises.validateTokenForAnonymousAccess.should.have.been.calledWith(
  180. this.project._id,
  181. this.token
  182. )
  183. })
  184. it('should give read-write access', function () {
  185. expect(this.result).to.equal('readAndWrite')
  186. })
  187. })
  188. describe('when the token is valid for read-only', function () {
  189. beforeEach(async function () {
  190. this.TokenAccessHandler.promises.validateTokenForAnonymousAccess =
  191. sinon
  192. .stub()
  193. .withArgs(this.project._id, this.token)
  194. .resolves({ isValidReadAndWrite: false, isValidReadOnly: true })
  195. this.result =
  196. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  197. null,
  198. this.project._id,
  199. this.token
  200. )
  201. })
  202. it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function () {
  203. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.called.should.equal(
  204. false
  205. )
  206. })
  207. it('should check if the token is valid', function () {
  208. this.TokenAccessHandler.promises.validateTokenForAnonymousAccess.should.have.been.calledWith(
  209. this.project._id,
  210. this.token
  211. )
  212. })
  213. it('should give read-only access', function () {
  214. expect(this.result).to.equal('readOnly')
  215. })
  216. })
  217. })
  218. })
  219. describe('with a private project', function () {
  220. beforeEach(function () {
  221. this.project.publicAccesLevel = 'private'
  222. })
  223. describe('with a user id with a privilege level', function () {
  224. beforeEach(async function () {
  225. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel
  226. .withArgs(this.user._id, this.project._id)
  227. .resolves(PrivilegeLevels.READ_ONLY)
  228. this.result =
  229. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  230. this.user._id,
  231. this.project._id,
  232. this.token
  233. )
  234. })
  235. it("should return the user's privilege level", function () {
  236. expect(this.result).to.equal('readOnly')
  237. })
  238. })
  239. describe('with a user id with no privilege level', function () {
  240. beforeEach(async function () {
  241. this.result =
  242. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  243. this.user._id,
  244. this.project._id,
  245. this.token
  246. )
  247. })
  248. it('should return false', function () {
  249. expect(this.result).to.equal(false)
  250. })
  251. })
  252. describe('with a user id who is an admin', function () {
  253. beforeEach(async function () {
  254. this.user.isAdmin = true
  255. this.result =
  256. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  257. this.user._id,
  258. this.project._id,
  259. this.token
  260. )
  261. })
  262. it('should return the user as an owner', function () {
  263. expect(this.result).to.equal('owner')
  264. })
  265. })
  266. describe('with no user (anonymous)', function () {
  267. beforeEach(async function () {
  268. this.result =
  269. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  270. null,
  271. this.project._id,
  272. this.token
  273. )
  274. })
  275. it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function () {
  276. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.called.should.equal(
  277. false
  278. )
  279. })
  280. it('should return false', function () {
  281. expect(this.result).to.equal(false)
  282. })
  283. })
  284. })
  285. describe('with a public project', function () {
  286. beforeEach(function () {
  287. this.project.publicAccesLevel = 'readAndWrite'
  288. })
  289. describe('with a user id with a privilege level', function () {
  290. beforeEach(async function () {
  291. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel
  292. .withArgs(this.user._id, this.project._id)
  293. .resolves(PrivilegeLevels.READ_ONLY)
  294. this.result =
  295. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  296. this.user._id,
  297. this.project._id,
  298. this.token
  299. )
  300. })
  301. it("should return the user's privilege level", function () {
  302. expect(this.result).to.equal('readOnly')
  303. })
  304. })
  305. describe('with a user id with no privilege level', function () {
  306. beforeEach(async function () {
  307. this.result =
  308. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  309. this.user._id,
  310. this.project._id,
  311. this.token
  312. )
  313. })
  314. it('should return the public privilege level', function () {
  315. expect(this.result).to.equal('readAndWrite')
  316. })
  317. })
  318. describe('with a user id who is an admin', function () {
  319. beforeEach(async function () {
  320. this.user.isAdmin = true
  321. this.result =
  322. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  323. this.user._id,
  324. this.project._id,
  325. this.token
  326. )
  327. })
  328. it('should return the user as an owner', function () {
  329. expect(this.result).to.equal('owner')
  330. })
  331. })
  332. describe('with no user (anonymous)', function () {
  333. beforeEach(async function () {
  334. this.result =
  335. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  336. null,
  337. this.project._id,
  338. this.token
  339. )
  340. })
  341. it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function () {
  342. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.called.should.equal(
  343. false
  344. )
  345. })
  346. it('should return the public privilege level', function () {
  347. expect(this.result).to.equal('readAndWrite')
  348. })
  349. })
  350. })
  351. describe("when the project doesn't exist", function () {
  352. it('should return a NotFoundError', async function () {
  353. const someOtherId = new ObjectId()
  354. await expect(
  355. this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  356. this.user._id,
  357. someOtherId,
  358. this.token
  359. )
  360. ).to.be.rejectedWith(Errors.NotFoundError)
  361. })
  362. })
  363. describe('when the project id is not valid', function () {
  364. beforeEach(function () {
  365. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel
  366. .withArgs(this.user._id, this.project._id)
  367. .resolves(PrivilegeLevels.READ_ONLY)
  368. })
  369. it('should return a error', async function () {
  370. await expect(
  371. this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  372. undefined,
  373. 'not project id',
  374. this.token
  375. )
  376. ).to.be.rejected
  377. })
  378. })
  379. })
  380. testPermission('canUserReadProject', {
  381. siteAdmin: true,
  382. owner: true,
  383. readAndWrite: true,
  384. readOnly: true,
  385. publicReadAndWrite: true,
  386. publicReadOnly: true,
  387. tokenReadAndWrite: true,
  388. tokenReadOnly: true,
  389. })
  390. testPermission('canUserWriteProjectContent', {
  391. siteAdmin: true,
  392. owner: true,
  393. readAndWrite: true,
  394. publicReadAndWrite: true,
  395. tokenReadAndWrite: true,
  396. })
  397. testPermission('canUserWriteProjectSettings', {
  398. siteAdmin: true,
  399. owner: true,
  400. readAndWrite: true,
  401. tokenReadAndWrite: true,
  402. })
  403. testPermission('canUserRenameProject', {
  404. siteAdmin: true,
  405. owner: true,
  406. })
  407. testPermission('canUserAdminProject', { siteAdmin: true, owner: true })
  408. describe('isUserSiteAdmin', function () {
  409. describe('when user is admin', function () {
  410. beforeEach(function () {
  411. this.user.isAdmin = true
  412. })
  413. it('should return true', async function () {
  414. const isAdmin =
  415. await this.AuthorizationManager.promises.isUserSiteAdmin(
  416. this.user._id
  417. )
  418. expect(isAdmin).to.equal(true)
  419. })
  420. })
  421. describe('when user is not admin', function () {
  422. it('should return false', async function () {
  423. const isAdmin =
  424. await this.AuthorizationManager.promises.isUserSiteAdmin(
  425. this.user._id
  426. )
  427. expect(isAdmin).to.equal(false)
  428. })
  429. })
  430. describe('when user is not found', function () {
  431. it('should return false', async function () {
  432. const someOtherId = new ObjectId()
  433. const isAdmin =
  434. await this.AuthorizationManager.promises.isUserSiteAdmin(someOtherId)
  435. expect(isAdmin).to.equal(false)
  436. })
  437. })
  438. describe('when no user is passed', function () {
  439. it('should return false', async function () {
  440. const isAdmin =
  441. await this.AuthorizationManager.promises.isUserSiteAdmin(null)
  442. expect(isAdmin).to.equal(false)
  443. })
  444. })
  445. })
  446. })
  447. function testPermission(permission, privilegeLevels) {
  448. describe(permission, function () {
  449. describe('when authenticated', function () {
  450. describe('when user is site admin', function () {
  451. beforeEach('set user as site admin', function () {
  452. this.user.isAdmin = true
  453. })
  454. expectPermission(permission, privilegeLevels.siteAdmin || false)
  455. })
  456. describe('when user is owner', function () {
  457. setupUserPrivilegeLevel(PrivilegeLevels.OWNER)
  458. expectPermission(permission, privilegeLevels.owner || false)
  459. })
  460. describe('when user has read-write access', function () {
  461. setupUserPrivilegeLevel(PrivilegeLevels.READ_AND_WRITE)
  462. expectPermission(permission, privilegeLevels.readAndWrite || false)
  463. })
  464. describe('when user has read-only access', function () {
  465. setupUserPrivilegeLevel(PrivilegeLevels.READ_ONLY)
  466. expectPermission(permission, privilegeLevels.readOnly || false)
  467. })
  468. describe('when user has read-write access as the public', function () {
  469. setupPublicAccessLevel(PublicAccessLevels.READ_AND_WRITE)
  470. expectPermission(
  471. permission,
  472. privilegeLevels.publicReadAndWrite || false
  473. )
  474. })
  475. describe('when user has read-only access as the public', function () {
  476. setupPublicAccessLevel(PublicAccessLevels.READ_ONLY)
  477. expectPermission(permission, privilegeLevels.publicReadOnly || false)
  478. })
  479. describe('when user is not found', function () {
  480. it('should return false', async function () {
  481. const otherUserId = new ObjectId()
  482. const value = await this.AuthorizationManager.promises[permission](
  483. otherUserId,
  484. this.project._id,
  485. this.token
  486. )
  487. expect(value).to.equal(false)
  488. })
  489. })
  490. })
  491. describe('when anonymous', function () {
  492. beforeEach(function () {
  493. this.user = null
  494. })
  495. describe('with read-write access through a token', function () {
  496. setupTokenAccessLevel('readAndWrite')
  497. expectPermission(permission, privilegeLevels.tokenReadAndWrite || false)
  498. })
  499. describe('with read-only access through a token', function () {
  500. setupTokenAccessLevel('readOnly')
  501. expectPermission(permission, privilegeLevels.tokenReadOnly || false)
  502. })
  503. describe('with public read-write access', function () {
  504. setupPublicAccessLevel(PublicAccessLevels.READ_AND_WRITE)
  505. expectPermission(
  506. permission,
  507. privilegeLevels.publicReadAndWrite || false
  508. )
  509. })
  510. describe('with public read-only access', function () {
  511. setupPublicAccessLevel(PublicAccessLevels.READ_ONLY)
  512. expectPermission(permission, privilegeLevels.publicReadOnly || false)
  513. })
  514. })
  515. })
  516. }
  517. function setupUserPrivilegeLevel(privilegeLevel) {
  518. beforeEach(`set user privilege level to ${privilegeLevel}`, function () {
  519. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel
  520. .withArgs(this.user._id, this.project._id)
  521. .resolves(privilegeLevel)
  522. })
  523. }
  524. function setupPublicAccessLevel(level) {
  525. beforeEach(`set public access level to ${level}`, function () {
  526. this.project.publicAccesLevel = level
  527. })
  528. }
  529. function setupTokenAccessLevel(level) {
  530. beforeEach(`set token access level to ${level}`, function () {
  531. this.project.publicAccesLevel = PublicAccessLevels.TOKEN_BASED
  532. this.TokenAccessHandler.promises.validateTokenForAnonymousAccess
  533. .withArgs(this.project._id, this.token)
  534. .resolves({
  535. isValidReadAndWrite: level === 'readAndWrite',
  536. isValidReadOnly: level === 'readOnly',
  537. })
  538. })
  539. }
  540. function expectPermission(permission, expectedValue) {
  541. it(`should return ${expectedValue}`, async function () {
  542. const value = await this.AuthorizationManager.promises[permission](
  543. this.user && this.user._id,
  544. this.project._id,
  545. this.token
  546. )
  547. expect(value).to.equal(expectedValue)
  548. })
  549. }