UserGetterTests.js 30 KB

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