UserGetterTests.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970
  1. const { ObjectId } = require('mongodb')
  2. const SandboxedModule = require('sandboxed-module')
  3. const assert = require('assert')
  4. const moment = require('moment')
  5. const path = require('path')
  6. const sinon = require('sinon')
  7. const modulePath = path.join(
  8. __dirname,
  9. '../../../../app/src/Features/User/UserGetter'
  10. )
  11. const { expect } = require('chai')
  12. const Errors = require('../../../../app/src/Features/Errors/Errors')
  13. const {
  14. normalizeQuery,
  15. normalizeMultiQuery,
  16. } = require('../../../../app/src/Features/Helpers/Mongo')
  17. describe('UserGetter', function () {
  18. beforeEach(function () {
  19. this.fakeUser = {
  20. _id: '12390i',
  21. email: 'email2@foo.bar',
  22. emails: [
  23. {
  24. email: 'email1@foo.bar',
  25. reversedHostname: 'rab.oof',
  26. confirmedAt: new Date(),
  27. },
  28. { email: 'email2@foo.bar', reversedHostname: 'rab.oof' },
  29. ],
  30. }
  31. this.findOne = sinon.stub().callsArgWith(2, null, this.fakeUser)
  32. this.findToArrayStub = sinon.stub().yields(null, [this.fakeUser])
  33. this.find = sinon.stub().returns({ toArray: this.findToArrayStub })
  34. this.Mongo = {
  35. db: {
  36. users: {
  37. findOne: this.findOne,
  38. find: this.find,
  39. },
  40. },
  41. ObjectId,
  42. }
  43. this.getUserAffiliations = sinon.stub().resolves([])
  44. this.UserGetter = SandboxedModule.require(modulePath, {
  45. requires: {
  46. '../Helpers/Mongo': { normalizeQuery, normalizeMultiQuery },
  47. '../../infrastructure/mongodb': this.Mongo,
  48. '@overleaf/metrics': {
  49. timeAsyncMethod: sinon.stub(),
  50. },
  51. '@overleaf/settings': (this.settings = {
  52. reconfirmNotificationDays: 14,
  53. }),
  54. '../Institutions/InstitutionsAPI': {
  55. promises: {
  56. getUserAffiliations: this.getUserAffiliations,
  57. },
  58. },
  59. '../../infrastructure/Features': {
  60. hasFeature: sinon.stub().returns(true),
  61. },
  62. '../../models/User': {
  63. User: (this.User = {}),
  64. },
  65. },
  66. })
  67. })
  68. describe('getSsoUsersAtInstitution', function () {
  69. it('should throw an error when no projection is passed', function (done) {
  70. this.UserGetter.getSsoUsersAtInstitution(1, undefined, error => {
  71. expect(error).to.exist
  72. expect(error.message).to.equal('missing projection')
  73. done()
  74. })
  75. })
  76. })
  77. describe('getUser', function () {
  78. it('should get user', function (done) {
  79. const query = { _id: '000000000000000000000000' }
  80. const projection = { email: 1 }
  81. this.UserGetter.getUser(query, projection, (error, user) => {
  82. expect(error).to.not.exist
  83. this.findOne.called.should.equal(true)
  84. this.findOne.calledWith(query, { projection }).should.equal(true)
  85. user.should.deep.equal(this.fakeUser)
  86. done()
  87. })
  88. })
  89. it('should not allow null query', function (done) {
  90. this.UserGetter.getUser(null, {}, error => {
  91. error.should.exist
  92. error.message.should.equal('no query provided')
  93. done()
  94. })
  95. })
  96. })
  97. describe('getUsers', function () {
  98. it('should get users with array of userIds', function (done) {
  99. const query = [new ObjectId()]
  100. const projection = { email: 1 }
  101. this.UserGetter.getUsers(query, projection, (error, users) => {
  102. expect(error).to.not.exist
  103. this.find.should.have.been.calledWithMatch(
  104. { _id: { $in: query } },
  105. { projection }
  106. )
  107. users.should.deep.equal([this.fakeUser])
  108. done()
  109. })
  110. })
  111. it('should not allow null query', function (done) {
  112. this.UserGetter.getUser(null, {}, error => {
  113. error.should.exist
  114. error.message.should.equal('no query provided')
  115. done()
  116. })
  117. })
  118. })
  119. describe('getUserFullEmails', function () {
  120. it('should get user', function (done) {
  121. this.UserGetter.promises.getUser = sinon.stub().resolves(this.fakeUser)
  122. const projection = { email: 1, emails: 1, samlIdentifiers: 1 }
  123. this.UserGetter.getUserFullEmails(
  124. this.fakeUser._id,
  125. (error, fullEmails) => {
  126. expect(error).to.not.exist
  127. this.UserGetter.promises.getUser.called.should.equal(true)
  128. this.UserGetter.promises.getUser
  129. .calledWith(this.fakeUser._id, projection)
  130. .should.equal(true)
  131. done()
  132. }
  133. )
  134. })
  135. it('should fetch emails data', function (done) {
  136. this.UserGetter.promises.getUser = sinon.stub().resolves(this.fakeUser)
  137. this.UserGetter.getUserFullEmails(
  138. this.fakeUser._id,
  139. (error, fullEmails) => {
  140. expect(error).to.not.exist
  141. assert.deepEqual(fullEmails, [
  142. {
  143. email: 'email1@foo.bar',
  144. reversedHostname: 'rab.oof',
  145. confirmedAt: this.fakeUser.emails[0].confirmedAt,
  146. emailHasInstitutionLicence: false,
  147. default: false,
  148. },
  149. {
  150. email: 'email2@foo.bar',
  151. reversedHostname: 'rab.oof',
  152. emailHasInstitutionLicence: false,
  153. default: true,
  154. },
  155. ])
  156. done()
  157. }
  158. )
  159. })
  160. it('should merge affiliation data', function (done) {
  161. this.UserGetter.promises.getUser = sinon.stub().resolves(this.fakeUser)
  162. const affiliationsData = [
  163. {
  164. email: 'email1@foo.bar',
  165. role: 'Prof',
  166. department: 'Maths',
  167. inferred: false,
  168. licence: 'pro_plus',
  169. institution: {
  170. name: 'University Name',
  171. isUniversity: true,
  172. confirmed: true,
  173. },
  174. portal: undefined,
  175. },
  176. ]
  177. this.getUserAffiliations.resolves(affiliationsData)
  178. this.UserGetter.getUserFullEmails(
  179. this.fakeUser._id,
  180. (error, fullEmails) => {
  181. expect(error).to.not.exist
  182. assert.deepEqual(fullEmails, [
  183. {
  184. email: 'email1@foo.bar',
  185. reversedHostname: 'rab.oof',
  186. confirmedAt: this.fakeUser.emails[0].confirmedAt,
  187. default: false,
  188. emailHasInstitutionLicence: true,
  189. affiliation: {
  190. institution: affiliationsData[0].institution,
  191. inferred: affiliationsData[0].inferred,
  192. department: affiliationsData[0].department,
  193. role: affiliationsData[0].role,
  194. lastDayToReconfirm: undefined,
  195. licence: affiliationsData[0].licence,
  196. inReconfirmNotificationPeriod: false,
  197. pastReconfirmDate: false,
  198. portal: undefined,
  199. },
  200. },
  201. {
  202. email: 'email2@foo.bar',
  203. reversedHostname: 'rab.oof',
  204. emailHasInstitutionLicence: false,
  205. default: true,
  206. },
  207. ])
  208. done()
  209. }
  210. )
  211. })
  212. it('should merge SAML identifier', function (done) {
  213. const fakeSamlIdentifiers = [
  214. { providerId: 'saml_id', exteranlUserId: 'whatever' },
  215. ]
  216. const fakeUserWithSaml = this.fakeUser
  217. fakeUserWithSaml.emails[0].samlProviderId = 'saml_id'
  218. fakeUserWithSaml.samlIdentifiers = fakeSamlIdentifiers
  219. this.UserGetter.promises.getUser = sinon.stub().resolves(this.fakeUser)
  220. this.getUserAffiliations.resolves([])
  221. this.UserGetter.getUserFullEmails(
  222. this.fakeUser._id,
  223. (error, fullEmails) => {
  224. expect(error).to.not.exist
  225. assert.deepEqual(fullEmails, [
  226. {
  227. email: 'email1@foo.bar',
  228. reversedHostname: 'rab.oof',
  229. confirmedAt: this.fakeUser.emails[0].confirmedAt,
  230. default: false,
  231. emailHasInstitutionLicence: false,
  232. samlProviderId: 'saml_id',
  233. samlIdentifier: fakeSamlIdentifiers[0],
  234. },
  235. {
  236. email: 'email2@foo.bar',
  237. reversedHostname: 'rab.oof',
  238. emailHasInstitutionLicence: false,
  239. default: true,
  240. },
  241. ])
  242. done()
  243. }
  244. )
  245. })
  246. it('should get user when it has no emails field', function (done) {
  247. this.fakeUser = {
  248. _id: '12390i',
  249. email: 'email2@foo.bar',
  250. }
  251. this.UserGetter.promises.getUser = sinon.stub().resolves(this.fakeUser)
  252. const projection = { email: 1, emails: 1, samlIdentifiers: 1 }
  253. this.UserGetter.getUserFullEmails(
  254. this.fakeUser._id,
  255. (error, fullEmails) => {
  256. expect(error).to.not.exist
  257. this.UserGetter.promises.getUser.called.should.equal(true)
  258. this.UserGetter.promises.getUser
  259. .calledWith(this.fakeUser._id, projection)
  260. .should.equal(true)
  261. assert.deepEqual(fullEmails, [])
  262. done()
  263. }
  264. )
  265. })
  266. describe('affiliation reconfirmation', function () {
  267. const institutionNonSSO = {
  268. id: 1,
  269. name: 'University Name',
  270. commonsAccount: true,
  271. isUniversity: true,
  272. confirmed: true,
  273. ssoBeta: false,
  274. ssoEnabled: false,
  275. maxConfirmationMonths: 12,
  276. }
  277. const institutionSSO = {
  278. id: 2,
  279. name: 'SSO University Name',
  280. isUniversity: true,
  281. confirmed: true,
  282. ssoBeta: false,
  283. ssoEnabled: true,
  284. maxConfirmationMonths: 12,
  285. }
  286. describe('non-SSO institutions', function () {
  287. const email1 = 'leonard@example-affiliation.com'
  288. const email2 = 'mccoy@example-affiliation.com'
  289. const affiliationsData = [
  290. {
  291. email: email1,
  292. role: 'Prof',
  293. department: 'Medicine',
  294. inferred: false,
  295. licence: 'pro_plus',
  296. institution: institutionNonSSO,
  297. },
  298. {
  299. email: email2,
  300. role: 'Prof',
  301. department: 'Medicine',
  302. inferred: false,
  303. licence: 'pro_plus',
  304. institution: institutionNonSSO,
  305. },
  306. ]
  307. it('should flag inReconfirmNotificationPeriod for all affiliations in period', function (done) {
  308. const user = {
  309. _id: '12390i',
  310. email: email1,
  311. emails: [
  312. {
  313. email: email1,
  314. reversedHostname: 'moc.noitailiffa-elpmaxe',
  315. confirmedAt: moment()
  316. .subtract(
  317. institutionNonSSO.maxConfirmationMonths + 2,
  318. 'months'
  319. )
  320. .toDate(),
  321. default: true,
  322. },
  323. {
  324. email: email2,
  325. reversedHostname: 'moc.noitailiffa-elpmaxe',
  326. confirmedAt: moment()
  327. .subtract(
  328. institutionNonSSO.maxConfirmationMonths + 1,
  329. 'months'
  330. )
  331. .toDate(),
  332. },
  333. ],
  334. }
  335. this.getUserAffiliations.resolves(affiliationsData)
  336. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  337. this.UserGetter.getUserFullEmails(
  338. this.fakeUser._id,
  339. (error, fullEmails) => {
  340. expect(error).to.not.exist
  341. expect(
  342. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  343. ).to.equal(true)
  344. expect(
  345. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  346. ).to.equal(true)
  347. done()
  348. }
  349. )
  350. })
  351. it('should not flag affiliations outside of notification period', function (done) {
  352. const aboutToBeWithinPeriod = moment()
  353. .subtract(institutionNonSSO.maxConfirmationMonths, 'months')
  354. .add(15, 'days')
  355. .toDate() // expires in 15 days
  356. const user = {
  357. _id: '12390i',
  358. email: email1,
  359. emails: [
  360. {
  361. email: email1,
  362. reversedHostname: 'moc.noitailiffa-elpmaxe',
  363. confirmedAt: new Date(),
  364. default: true,
  365. },
  366. {
  367. email: email2,
  368. reversedHostname: 'moc.noitailiffa-elpmaxe',
  369. confirmedAt: aboutToBeWithinPeriod,
  370. },
  371. ],
  372. }
  373. this.getUserAffiliations.resolves(affiliationsData)
  374. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  375. this.UserGetter.getUserFullEmails(
  376. this.fakeUser._id,
  377. (error, fullEmails) => {
  378. expect(error).to.not.exist
  379. expect(
  380. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  381. ).to.equal(false)
  382. expect(
  383. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  384. ).to.equal(false)
  385. done()
  386. }
  387. )
  388. })
  389. })
  390. describe('SSO institutions', function () {
  391. it('should flag only linked email, if in notification period', function (done) {
  392. const email1 = 'email1@sso.bar'
  393. const email2 = 'email2@sso.bar'
  394. const email3 = 'email3@sso.bar'
  395. const affiliationsData = [
  396. {
  397. email: email1,
  398. role: 'Prof',
  399. department: 'Maths',
  400. inferred: false,
  401. licence: 'pro_plus',
  402. institution: institutionSSO,
  403. },
  404. {
  405. email: email2,
  406. role: 'Prof',
  407. department: 'Maths',
  408. inferred: false,
  409. licence: 'pro_plus',
  410. institution: institutionSSO,
  411. },
  412. {
  413. email: email3,
  414. role: 'Prof',
  415. department: 'Maths',
  416. inferred: false,
  417. licence: 'pro_plus',
  418. institution: institutionSSO,
  419. },
  420. ]
  421. const user = {
  422. _id: '12390i',
  423. email: email1,
  424. emails: [
  425. {
  426. email: email1,
  427. reversedHostname: 'rab.oss',
  428. confirmedAt: new Date('2019-09-24'),
  429. reconfirmedAt: new Date('2019-09-24'),
  430. default: true,
  431. },
  432. {
  433. email: email2,
  434. reversedHostname: 'rab.oss',
  435. confirmedAt: new Date('2019-09-24'),
  436. reconfirmedAt: new Date('2019-09-24'),
  437. samlProviderId: institutionSSO.id,
  438. },
  439. {
  440. email: email3,
  441. reversedHostname: 'rab.oss',
  442. confirmedAt: new Date('2019-09-24'),
  443. reconfirmedAt: new Date('2019-09-24'),
  444. },
  445. ],
  446. samlIdentifiers: [
  447. {
  448. providerId: institutionSSO.id,
  449. externalUserId: 'abc123',
  450. },
  451. ],
  452. }
  453. this.getUserAffiliations.resolves(affiliationsData)
  454. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  455. this.UserGetter.getUserFullEmails(
  456. this.fakeUser._id,
  457. (error, fullEmails) => {
  458. expect(error).to.not.exist
  459. expect(
  460. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  461. ).to.equal(false)
  462. expect(
  463. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  464. ).to.equal(true)
  465. expect(
  466. fullEmails[2].affiliation.inReconfirmNotificationPeriod
  467. ).to.equal(false)
  468. done()
  469. }
  470. )
  471. })
  472. })
  473. describe('multiple institution affiliations', function () {
  474. it('should flag each institution', function (done) {
  475. const email1 = 'email1@sso.bar'
  476. const email2 = 'email2@sso.bar'
  477. const email3 = 'email3@foo.bar'
  478. const email4 = 'email4@foo.bar'
  479. const affiliationsData = [
  480. {
  481. email: email1,
  482. role: 'Prof',
  483. department: 'Maths',
  484. inferred: false,
  485. licence: 'pro_plus',
  486. institution: institutionSSO,
  487. },
  488. {
  489. email: email2,
  490. role: 'Prof',
  491. department: 'Maths',
  492. inferred: false,
  493. licence: 'pro_plus',
  494. institution: institutionSSO,
  495. },
  496. {
  497. email: email3,
  498. role: 'Prof',
  499. department: 'Maths',
  500. inferred: false,
  501. licence: 'pro_plus',
  502. institution: institutionNonSSO,
  503. },
  504. {
  505. email: email4,
  506. role: 'Prof',
  507. department: 'Maths',
  508. inferred: false,
  509. licence: 'pro_plus',
  510. institution: institutionNonSSO,
  511. },
  512. ]
  513. const user = {
  514. _id: '12390i',
  515. email: email1,
  516. emails: [
  517. {
  518. email: email1,
  519. reversedHostname: 'rab.oss',
  520. confirmedAt: '2019-09-24T20:25:08.503Z',
  521. default: true,
  522. },
  523. {
  524. email: email2,
  525. reversedHostname: 'rab.oss',
  526. confirmedAt: new Date('2019-09-24T20:25:08.503Z'),
  527. samlProviderId: institutionSSO.id,
  528. },
  529. {
  530. email: email3,
  531. reversedHostname: 'rab.oof',
  532. confirmedAt: new Date('2019-10-24T20:25:08.503Z'),
  533. },
  534. {
  535. email: email4,
  536. reversedHostname: 'rab.oof',
  537. confirmedAt: new Date('2019-09-24T20:25:08.503Z'),
  538. },
  539. ],
  540. samlIdentifiers: [
  541. {
  542. providerId: institutionSSO.id,
  543. externalUserId: 'abc123',
  544. },
  545. ],
  546. }
  547. this.getUserAffiliations.resolves(affiliationsData)
  548. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  549. this.UserGetter.getUserFullEmails(
  550. this.fakeUser._id,
  551. (error, fullEmails) => {
  552. expect(error).to.not.exist
  553. expect(
  554. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  555. ).to.to.equal(false)
  556. expect(
  557. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  558. ).to.equal(true)
  559. expect(
  560. fullEmails[2].affiliation.inReconfirmNotificationPeriod
  561. ).to.equal(true)
  562. expect(
  563. fullEmails[3].affiliation.inReconfirmNotificationPeriod
  564. ).to.equal(true)
  565. done()
  566. }
  567. )
  568. })
  569. })
  570. describe('reconfirmedAt', function () {
  571. it('only use confirmedAt when no reconfirmedAt', function (done) {
  572. const email1 = 'email1@foo.bar'
  573. const email2 = 'email2@foo.bar'
  574. const email3 = 'email3@foo.bar'
  575. const affiliationsData = [
  576. {
  577. email: email1,
  578. role: 'Prof',
  579. department: 'Maths',
  580. inferred: false,
  581. licence: 'pro_plus',
  582. institution: institutionNonSSO,
  583. },
  584. {
  585. email: email2,
  586. role: 'Prof',
  587. department: 'Maths',
  588. inferred: false,
  589. licence: 'pro_plus',
  590. institution: institutionNonSSO,
  591. },
  592. {
  593. email: email3,
  594. role: 'Prof',
  595. department: 'Maths',
  596. inferred: false,
  597. licence: 'pro_plus',
  598. institution: institutionNonSSO,
  599. },
  600. ]
  601. const user = {
  602. _id: '12390i',
  603. email: email1,
  604. emails: [
  605. {
  606. email: email1,
  607. reversedHostname: 'rab.oof',
  608. confirmedAt: moment().subtract(
  609. institutionSSO.maxConfirmationMonths * 2,
  610. 'months'
  611. ),
  612. reconfirmedAt: moment().subtract(
  613. institutionSSO.maxConfirmationMonths * 3,
  614. 'months'
  615. ),
  616. default: true,
  617. },
  618. {
  619. email: email2,
  620. reversedHostname: 'rab.oof',
  621. confirmedAt: moment().subtract(
  622. institutionSSO.maxConfirmationMonths * 3,
  623. 'months'
  624. ),
  625. reconfirmedAt: moment().subtract(
  626. institutionSSO.maxConfirmationMonths * 2,
  627. 'months'
  628. ),
  629. },
  630. {
  631. email: email3,
  632. reversedHostname: 'rab.oof',
  633. confirmedAt: moment().subtract(
  634. institutionSSO.maxConfirmationMonths * 4,
  635. 'months'
  636. ),
  637. reconfirmedAt: moment().subtract(
  638. institutionSSO.maxConfirmationMonths * 4,
  639. 'months'
  640. ),
  641. },
  642. ],
  643. }
  644. this.getUserAffiliations.resolves(affiliationsData)
  645. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  646. this.UserGetter.getUserFullEmails(
  647. this.fakeUser._id,
  648. (error, fullEmails) => {
  649. expect(error).to.not.exist
  650. expect(
  651. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  652. ).to.equal(true)
  653. expect(
  654. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  655. ).to.equal(true)
  656. expect(
  657. fullEmails[2].affiliation.inReconfirmNotificationPeriod
  658. ).to.equal(true)
  659. done()
  660. }
  661. )
  662. })
  663. })
  664. describe('before reconfirmation period expires and within reconfirmation notification period', function () {
  665. const email = 'leonard@example-affiliation.com'
  666. it('should flag the email', function (done) {
  667. const confirmedAt = moment()
  668. .subtract(institutionNonSSO.maxConfirmationMonths, 'months')
  669. .subtract(14, 'days')
  670. .toDate() // expires in 14 days
  671. const affiliationsData = [
  672. {
  673. email,
  674. role: 'Prof',
  675. department: 'Medicine',
  676. inferred: false,
  677. licence: 'pro_plus',
  678. institution: institutionNonSSO,
  679. },
  680. ]
  681. const user = {
  682. _id: '12390i',
  683. email,
  684. emails: [
  685. {
  686. email,
  687. confirmedAt,
  688. default: true,
  689. },
  690. ],
  691. }
  692. this.getUserAffiliations.resolves(affiliationsData)
  693. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  694. this.UserGetter.getUserFullEmails(
  695. this.fakeUser._id,
  696. (error, fullEmails) => {
  697. expect(error).to.not.exist
  698. expect(
  699. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  700. ).to.equal(true)
  701. done()
  702. }
  703. )
  704. })
  705. })
  706. describe('when no Settings.reconfirmNotificationDays', function () {
  707. it('should always return inReconfirmNotificationPeriod:false', function (done) {
  708. const email1 = 'email1@sso.bar'
  709. const email2 = 'email2@foo.bar'
  710. const email3 = 'email3@foo.bar'
  711. const confirmedAtAboutToExpire = moment()
  712. .subtract(institutionNonSSO.maxConfirmationMonths, 'months')
  713. .subtract(14, 'days')
  714. .toDate() // expires in 14 days
  715. const affiliationsData = [
  716. {
  717. email: email1,
  718. institution: institutionSSO,
  719. },
  720. {
  721. email: email2,
  722. institution: institutionNonSSO,
  723. },
  724. {
  725. email: email3,
  726. institution: institutionNonSSO,
  727. },
  728. ]
  729. const user = {
  730. _id: '12390i',
  731. email: email1,
  732. emails: [
  733. {
  734. email: email1,
  735. confirmedAt: confirmedAtAboutToExpire,
  736. default: true,
  737. samlProviderId: institutionSSO.id,
  738. },
  739. {
  740. email: email2,
  741. confirmedAt: new Date('2019-09-24T20:25:08.503Z'),
  742. },
  743. {
  744. email: email3,
  745. confirmedAt: new Date('2019-10-24T20:25:08.503Z'),
  746. },
  747. ],
  748. samlIdentifiers: [
  749. {
  750. providerId: institutionSSO.id,
  751. externalUserId: 'abc123',
  752. },
  753. ],
  754. }
  755. this.settings.reconfirmNotificationDays = undefined
  756. this.getUserAffiliations.resolves(affiliationsData)
  757. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  758. this.UserGetter.getUserFullEmails(
  759. this.fakeUser._id,
  760. (error, fullEmails) => {
  761. expect(error).to.not.exist
  762. expect(
  763. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  764. ).to.to.equal(false)
  765. expect(
  766. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  767. ).to.equal(false)
  768. expect(
  769. fullEmails[2].affiliation.inReconfirmNotificationPeriod
  770. ).to.equal(false)
  771. done()
  772. }
  773. )
  774. })
  775. })
  776. })
  777. })
  778. describe('getUserbyMainEmail', function () {
  779. it('query user by main email', function (done) {
  780. const email = 'hello@world.com'
  781. const projection = { emails: 1 }
  782. this.UserGetter.getUserByMainEmail(email, projection, (error, user) => {
  783. expect(error).to.not.exist
  784. this.findOne.called.should.equal(true)
  785. this.findOne.calledWith({ email }, { projection }).should.equal(true)
  786. done()
  787. })
  788. })
  789. it('return user if found', function (done) {
  790. const email = 'hello@world.com'
  791. this.UserGetter.getUserByMainEmail(email, (error, user) => {
  792. expect(error).to.not.exist
  793. user.should.deep.equal(this.fakeUser)
  794. done()
  795. })
  796. })
  797. it('trim email', function (done) {
  798. const email = 'hello@world.com'
  799. this.UserGetter.getUserByMainEmail(` ${email} `, (error, user) => {
  800. expect(error).to.not.exist
  801. this.findOne.called.should.equal(true)
  802. this.findOne.calledWith({ email }).should.equal(true)
  803. done()
  804. })
  805. })
  806. })
  807. describe('getUserByAnyEmail', function () {
  808. it('query user for any email', function (done) {
  809. const email = 'hello@world.com'
  810. const expectedQuery = {
  811. emails: { $exists: true },
  812. 'emails.email': email,
  813. }
  814. const projection = { emails: 1 }
  815. this.UserGetter.getUserByAnyEmail(
  816. ` ${email} `,
  817. projection,
  818. (error, user) => {
  819. expect(error).to.not.exist
  820. this.findOne
  821. .calledWith(expectedQuery, { projection })
  822. .should.equal(true)
  823. user.should.deep.equal(this.fakeUser)
  824. done()
  825. }
  826. )
  827. })
  828. it('query contains $exists:true so partial index is used', function (done) {
  829. const expectedQuery = {
  830. emails: { $exists: true },
  831. 'emails.email': '',
  832. }
  833. this.UserGetter.getUserByAnyEmail('', {}, (error, user) => {
  834. expect(error).to.not.exist
  835. this.findOne
  836. .calledWith(expectedQuery, { projection: {} })
  837. .should.equal(true)
  838. done()
  839. })
  840. })
  841. it('checks main email as well', function (done) {
  842. this.findOne.callsArgWith(2, null, null)
  843. const email = 'hello@world.com'
  844. const projection = { emails: 1 }
  845. this.UserGetter.getUserByAnyEmail(
  846. ` ${email} `,
  847. projection,
  848. (error, user) => {
  849. expect(error).to.not.exist
  850. this.findOne.calledTwice.should.equal(true)
  851. this.findOne.calledWith({ email }, { projection }).should.equal(true)
  852. done()
  853. }
  854. )
  855. })
  856. })
  857. describe('getUsersByHostname', function () {
  858. it('should find user by hostname', function (done) {
  859. const hostname = 'bar.foo'
  860. const expectedQuery = {
  861. emails: { $exists: true },
  862. 'emails.reversedHostname': hostname.split('').reverse().join(''),
  863. }
  864. const projection = { emails: 1 }
  865. this.UserGetter.getUsersByHostname(
  866. hostname,
  867. projection,
  868. (error, users) => {
  869. expect(error).to.not.exist
  870. this.find.calledOnce.should.equal(true)
  871. this.find.calledWith(expectedQuery, { projection }).should.equal(true)
  872. done()
  873. }
  874. )
  875. })
  876. })
  877. describe('getUsersByAnyConfirmedEmail', function () {
  878. it('should find users by confirmed email', function (done) {
  879. const emails = ['confirmed@example.com']
  880. this.UserGetter.getUsersByAnyConfirmedEmail(emails, (error, users) => {
  881. expect(error).to.not.exist
  882. expect(this.find).to.be.calledOnceWith(
  883. {
  884. 'emails.email': { $in: emails }, // use the index on emails.email
  885. emails: {
  886. $exists: true,
  887. $elemMatch: {
  888. email: { $in: emails },
  889. confirmedAt: { $exists: true },
  890. },
  891. },
  892. },
  893. { projection: {} }
  894. )
  895. done()
  896. })
  897. })
  898. })
  899. describe('getUsersByV1Id', function () {
  900. it('should find users by list of v1 ids', function (done) {
  901. const v1Ids = [501]
  902. const expectedQuery = {
  903. 'overleaf.id': { $in: v1Ids },
  904. }
  905. const projection = { emails: 1 }
  906. this.UserGetter.getUsersByV1Ids(v1Ids, projection, (error, users) => {
  907. expect(error).to.not.exist
  908. this.find.calledOnce.should.equal(true)
  909. this.find.calledWith(expectedQuery, { projection }).should.equal(true)
  910. done()
  911. })
  912. })
  913. })
  914. describe('ensureUniqueEmailAddress', function () {
  915. beforeEach(function () {
  916. this.UserGetter.getUserByAnyEmail = sinon.stub()
  917. })
  918. it('should return error if existing user is found', function (done) {
  919. this.UserGetter.getUserByAnyEmail.callsArgWith(1, null, this.fakeUser)
  920. this.UserGetter.ensureUniqueEmailAddress(this.newEmail, err => {
  921. expect(err).to.exist
  922. expect(err).to.be.an.instanceof(Errors.EmailExistsError)
  923. done()
  924. })
  925. })
  926. it('should return null if no user is found', function (done) {
  927. this.UserGetter.getUserByAnyEmail.callsArgWith(1)
  928. this.UserGetter.ensureUniqueEmailAddress(this.newEmail, err => {
  929. expect(err).not.to.exist
  930. done()
  931. })
  932. })
  933. })
  934. })