UserGetterTests.js 30 KB

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