AuthorizationManagerTests.js 19 KB

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