mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #3492 from overleaf/jel-calc-notification-period
Flag emails in affiliation notification period GitOrigin-RevId: d2d4868ba3a49e69b85a3bdca16d12276ac5c006
This commit is contained in:
parent
37f083809a
commit
b44aed56dd
6 changed files with 804 additions and 41 deletions
|
@ -2,6 +2,8 @@ const { callbackify } = require('util')
|
||||||
const { db } = require('../../infrastructure/mongodb')
|
const { db } = require('../../infrastructure/mongodb')
|
||||||
const metrics = require('@overleaf/metrics')
|
const metrics = require('@overleaf/metrics')
|
||||||
const logger = require('logger-sharelatex')
|
const logger = require('logger-sharelatex')
|
||||||
|
const moment = require('moment')
|
||||||
|
const settings = require('settings-sharelatex')
|
||||||
const { promisifyAll } = require('../../util/promises')
|
const { promisifyAll } = require('../../util/promises')
|
||||||
const {
|
const {
|
||||||
promises: InstitutionsAPIPromises
|
promises: InstitutionsAPIPromises
|
||||||
|
@ -11,6 +13,35 @@ const Errors = require('../Errors/Errors')
|
||||||
const Features = require('../../infrastructure/Features')
|
const Features = require('../../infrastructure/Features')
|
||||||
const { normalizeQuery, normalizeMultiQuery } = require('../Helpers/Mongo')
|
const { normalizeQuery, normalizeMultiQuery } = require('../Helpers/Mongo')
|
||||||
|
|
||||||
|
function _emailInReconfirmNotificationPeriod(emailData, institutionData) {
|
||||||
|
const globalReconfirmPeriod = settings.reconfirmNotificationDays
|
||||||
|
if (!globalReconfirmPeriod) return false
|
||||||
|
|
||||||
|
// only show notification for institutions with reconfirmation enabled
|
||||||
|
if (!institutionData || !institutionData.maxConfirmationMonths) return false
|
||||||
|
|
||||||
|
if (!emailData.confirmedAt) return false
|
||||||
|
|
||||||
|
if (institutionData.ssoEnabled && !emailData.samlProviderId) {
|
||||||
|
// For SSO, only show notification for linked email
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// reconfirmedAt will not always be set, use confirmedAt as fallback
|
||||||
|
const lastConfirmed = emailData.reconfirmedAt || emailData.confirmedAt
|
||||||
|
|
||||||
|
const lastDayToReconfirm = moment(lastConfirmed).add(
|
||||||
|
institutionData.maxConfirmationMonths,
|
||||||
|
'months'
|
||||||
|
)
|
||||||
|
const notificationStarts = moment(lastDayToReconfirm).subtract(
|
||||||
|
globalReconfirmPeriod,
|
||||||
|
'days'
|
||||||
|
)
|
||||||
|
|
||||||
|
return moment().isAfter(notificationStarts)
|
||||||
|
}
|
||||||
|
|
||||||
async function getUserFullEmails(userId) {
|
async function getUserFullEmails(userId) {
|
||||||
const user = await UserGetter.promises.getUser(userId, {
|
const user = await UserGetter.promises.getUser(userId, {
|
||||||
email: 1,
|
email: 1,
|
||||||
|
@ -155,8 +186,8 @@ var decorateFullEmails = (
|
||||||
emailsData,
|
emailsData,
|
||||||
affiliationsData,
|
affiliationsData,
|
||||||
samlIdentifiers
|
samlIdentifiers
|
||||||
) =>
|
) => {
|
||||||
emailsData.map(function(emailData) {
|
emailsData.forEach(function(emailData) {
|
||||||
emailData.default = emailData.email === defaultEmail
|
emailData.default = emailData.email === defaultEmail
|
||||||
|
|
||||||
const affiliation = affiliationsData.find(
|
const affiliation = affiliationsData.find(
|
||||||
|
@ -164,31 +195,33 @@ var decorateFullEmails = (
|
||||||
)
|
)
|
||||||
if (affiliation) {
|
if (affiliation) {
|
||||||
const { institution, inferred, role, department, licence } = affiliation
|
const { institution, inferred, role, department, licence } = affiliation
|
||||||
|
const inReconfirmNotificationPeriod = _emailInReconfirmNotificationPeriod(
|
||||||
|
emailData,
|
||||||
|
institution
|
||||||
|
)
|
||||||
emailData.affiliation = {
|
emailData.affiliation = {
|
||||||
institution,
|
institution,
|
||||||
inferred,
|
inferred,
|
||||||
|
inReconfirmNotificationPeriod,
|
||||||
role,
|
role,
|
||||||
department,
|
department,
|
||||||
licence
|
licence
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
emailsData.affiliation = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (emailData.samlProviderId) {
|
if (emailData.samlProviderId) {
|
||||||
emailData.samlIdentifier = samlIdentifiers.find(
|
emailData.samlIdentifier = samlIdentifiers.find(
|
||||||
samlIdentifier => samlIdentifier.providerId === emailData.samlProviderId
|
samlIdentifier => samlIdentifier.providerId === emailData.samlProviderId
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
emailsData.samlIdentifier = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emailData.emailHasInstitutionLicence = InstitutionsHelper.emailHasLicence(
|
emailData.emailHasInstitutionLicence = InstitutionsHelper.emailHasLicence(
|
||||||
emailData
|
emailData
|
||||||
)
|
)
|
||||||
|
|
||||||
return emailData
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return emailsData
|
||||||
|
}
|
||||||
;[
|
;[
|
||||||
'getUser',
|
'getUser',
|
||||||
'getUserEmail',
|
'getUserEmail',
|
||||||
|
|
|
@ -186,3 +186,5 @@ module.exports =
|
||||||
# setting to true since many features are enabled/disabled after availability of this
|
# setting to true since many features are enabled/disabled after availability of this
|
||||||
# property (check Features.js)
|
# property (check Features.js)
|
||||||
overleaf: true
|
overleaf: true
|
||||||
|
|
||||||
|
reconfirmNotificationDays: 14
|
||||||
|
|
|
@ -516,6 +516,7 @@ describe('Subscriptions', function() {
|
||||||
department: 'Math',
|
department: 'Math',
|
||||||
role: 'Prof',
|
role: 'Prof',
|
||||||
inferred: false,
|
inferred: false,
|
||||||
|
inReconfirmNotificationPeriod: false,
|
||||||
institution: {
|
institution: {
|
||||||
name: 'Stanford',
|
name: 'Stanford',
|
||||||
confirmed: true
|
confirmed: true
|
||||||
|
|
|
@ -1,39 +1,14 @@
|
||||||
const { expect } = require('chai')
|
const { expect } = require('chai')
|
||||||
const async = require('async')
|
const async = require('async')
|
||||||
|
const moment = require('moment')
|
||||||
|
const Features = require('../../../app/src/infrastructure/Features')
|
||||||
const User = require('./helpers/User')
|
const User = require('./helpers/User')
|
||||||
const UserHelper = require('./helpers/UserHelper')
|
const UserHelper = require('./helpers/UserHelper')
|
||||||
|
const UserUpdater = require('../../../app/src/Features/User/UserUpdater')
|
||||||
const { db, ObjectId } = require('../../../app/src/infrastructure/mongodb')
|
const { db, ObjectId } = require('../../../app/src/infrastructure/mongodb')
|
||||||
const MockV1Api = require('./helpers/MockV1Api')
|
const MockV1Api = require('./helpers/MockV1Api')
|
||||||
const expectErrorResponse = require('./helpers/expectErrorResponse')
|
const expectErrorResponse = require('./helpers/expectErrorResponse')
|
||||||
|
|
||||||
async function confirmEmail(userHelper, email) {
|
|
||||||
let response
|
|
||||||
// UserHelper.createUser does not create a confirmation token
|
|
||||||
response = await userHelper.request.post({
|
|
||||||
form: {
|
|
||||||
email
|
|
||||||
},
|
|
||||||
simple: false,
|
|
||||||
uri: '/user/emails/resend_confirmation'
|
|
||||||
})
|
|
||||||
expect(response.statusCode).to.equal(200)
|
|
||||||
const tokenData = await db.tokens
|
|
||||||
.find({
|
|
||||||
use: 'email_confirmation',
|
|
||||||
'data.user_id': userHelper.user._id.toString(),
|
|
||||||
usedAt: { $exists: false }
|
|
||||||
})
|
|
||||||
.next()
|
|
||||||
response = await userHelper.request.post({
|
|
||||||
form: {
|
|
||||||
token: tokenData.token
|
|
||||||
},
|
|
||||||
simple: false,
|
|
||||||
uri: '/user/emails/confirm'
|
|
||||||
})
|
|
||||||
expect(response.statusCode).to.equal(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('UserEmails', function() {
|
describe('UserEmails', function() {
|
||||||
beforeEach(function(done) {
|
beforeEach(function(done) {
|
||||||
this.timeout(20000)
|
this.timeout(20000)
|
||||||
|
@ -283,14 +258,14 @@ describe('UserEmails', function() {
|
||||||
password: userHelper.getDefaultPassword()
|
password: userHelper.getDefaultPassword()
|
||||||
})
|
})
|
||||||
// original confirmation
|
// original confirmation
|
||||||
await confirmEmail(userHelper, email)
|
await userHelper.confirmEmail(userHelper.user._id, email)
|
||||||
const user = (await UserHelper.getUser({ email })).user
|
const user = (await UserHelper.getUser({ email })).user
|
||||||
confirmedAtDate = user.emails[0].confirmedAt
|
confirmedAtDate = user.emails[0].confirmedAt
|
||||||
expect(user.emails[0].confirmedAt).to.exist
|
expect(user.emails[0].confirmedAt).to.exist
|
||||||
expect(user.emails[0].reconfirmedAt).to.exist
|
expect(user.emails[0].reconfirmedAt).to.exist
|
||||||
})
|
})
|
||||||
it('should set reconfirmedAt and not reset confirmedAt', async function() {
|
it('should set reconfirmedAt and not reset confirmedAt', async function() {
|
||||||
await confirmEmail(userHelper, email)
|
await userHelper.confirmEmail(userHelper.user._id, email)
|
||||||
const user = (await UserHelper.getUser({ email })).user
|
const user = (await UserHelper.getUser({ email })).user
|
||||||
expect(user.emails[0].confirmedAt).to.exist
|
expect(user.emails[0].confirmedAt).to.exist
|
||||||
expect(user.emails[0].reconfirmedAt).to.exist
|
expect(user.emails[0].reconfirmedAt).to.exist
|
||||||
|
@ -1019,4 +994,159 @@ describe('UserEmails', function() {
|
||||||
expect(user.auditLog[0].ip).to.equal(this.user.request.ip)
|
expect(user.auditLog[0].ip).to.equal(this.user.request.ip)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('notification period', function() {
|
||||||
|
let defaultEmail, userHelper, email1, email2, email3
|
||||||
|
const maxConfirmationMonths = 12
|
||||||
|
|
||||||
|
beforeEach(async function() {
|
||||||
|
if (!Features.hasFeature('affiliations')) {
|
||||||
|
this.skip()
|
||||||
|
}
|
||||||
|
userHelper = new UserHelper()
|
||||||
|
defaultEmail = userHelper.getDefaultEmail()
|
||||||
|
userHelper = await UserHelper.createUser({ email: defaultEmail })
|
||||||
|
userHelper = await UserHelper.loginUser({
|
||||||
|
email: defaultEmail,
|
||||||
|
password: userHelper.getDefaultPassword()
|
||||||
|
})
|
||||||
|
const institutionId = MockV1Api.createInstitution({
|
||||||
|
ssoEnabled: false,
|
||||||
|
maxConfirmationMonths
|
||||||
|
})
|
||||||
|
const domain = 'example-affiliation.com'
|
||||||
|
MockV1Api.addInstitutionDomain(institutionId, domain)
|
||||||
|
|
||||||
|
email1 = `leonard@${domain}`
|
||||||
|
email2 = `mccoy@${domain}`
|
||||||
|
email3 = `bones@${domain}`
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('non SSO affiliations', function() {
|
||||||
|
beforeEach(async function() {
|
||||||
|
// create a user with 3 affiliations at the institution.
|
||||||
|
// all are within in the notification period
|
||||||
|
const backdatedDays = maxConfirmationMonths * 2 * 30
|
||||||
|
const userId = userHelper.user._id
|
||||||
|
await userHelper.addEmailAndConfirm(userId, email1)
|
||||||
|
await userHelper.addEmailAndConfirm(userId, email2)
|
||||||
|
await userHelper.addEmailAndConfirm(userId, email3)
|
||||||
|
await userHelper.backdateConfirmation(userId, email1, backdatedDays)
|
||||||
|
await userHelper.backdateConfirmation(userId, email2, backdatedDays)
|
||||||
|
await userHelper.backdateConfirmation(userId, email3, backdatedDays)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should flag inReconfirmNotificationPeriod for all affiliations in period', async function() {
|
||||||
|
const response = await userHelper.request.get('/user/emails')
|
||||||
|
expect(response.statusCode).to.equal(200)
|
||||||
|
const fullEmails = JSON.parse(response.body)
|
||||||
|
expect(fullEmails.length).to.equal(4)
|
||||||
|
expect(fullEmails[0].affiliation).to.not.exist
|
||||||
|
expect(
|
||||||
|
fullEmails[1].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
expect(
|
||||||
|
fullEmails[2].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
expect(
|
||||||
|
fullEmails[3].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('should flag emails before their confirmation expires, but within the notification period', function() {
|
||||||
|
beforeEach(async function() {
|
||||||
|
const dateInPeriodButNotExpired = moment()
|
||||||
|
.subtract(maxConfirmationMonths, 'months')
|
||||||
|
.add(14, 'days')
|
||||||
|
.toDate()
|
||||||
|
const backdatedDays = moment().diff(dateInPeriodButNotExpired, 'days')
|
||||||
|
await userHelper.backdateConfirmation(
|
||||||
|
userHelper.user._id,
|
||||||
|
email1,
|
||||||
|
backdatedDays
|
||||||
|
)
|
||||||
|
await userHelper.backdateConfirmation(
|
||||||
|
userHelper.user._id,
|
||||||
|
email2,
|
||||||
|
backdatedDays
|
||||||
|
)
|
||||||
|
await userHelper.backdateConfirmation(
|
||||||
|
userHelper.user._id,
|
||||||
|
email3,
|
||||||
|
backdatedDays
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should flag the emails', async function() {
|
||||||
|
const response = await userHelper.request.get('/user/emails')
|
||||||
|
expect(response.statusCode).to.equal(200)
|
||||||
|
const fullEmails = JSON.parse(response.body)
|
||||||
|
expect(fullEmails.length).to.equal(4)
|
||||||
|
expect(fullEmails[0].affiliation).to.not.exist
|
||||||
|
expect(
|
||||||
|
fullEmails[1].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
expect(
|
||||||
|
fullEmails[2].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
expect(
|
||||||
|
fullEmails[3].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
// ensure dates are not past reconfirmation period
|
||||||
|
function _getLastDayToReconfirm(date) {
|
||||||
|
return moment(date).add(maxConfirmationMonths, 'months')
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(
|
||||||
|
moment(fullEmails[1].reconfirmedAt).isAfter(
|
||||||
|
_getLastDayToReconfirm(fullEmails[1].reconfirmedAt)
|
||||||
|
)
|
||||||
|
).to.equal(false)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
moment(fullEmails[2].reconfirmedAt).isAfter(
|
||||||
|
_getLastDayToReconfirm(fullEmails[2].reconfirmedAt)
|
||||||
|
)
|
||||||
|
).to.equal(false)
|
||||||
|
expect(
|
||||||
|
moment(fullEmails[3].reconfirmedAt).isAfter(
|
||||||
|
_getLastDayToReconfirm(fullEmails[3].reconfirmedAt)
|
||||||
|
)
|
||||||
|
).to.equal(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('missing reconfirmedAt', function() {
|
||||||
|
beforeEach(async function() {
|
||||||
|
const userId = userHelper.user._id
|
||||||
|
const query = {
|
||||||
|
_id: userId,
|
||||||
|
'emails.email': email2
|
||||||
|
}
|
||||||
|
const update = {
|
||||||
|
$unset: { 'emails.$.reconfirmedAt': true }
|
||||||
|
}
|
||||||
|
await UserUpdater.promises.updateUser(query, update)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fallback to confirmedAt for date check', async function() {
|
||||||
|
const response = await userHelper.request.get('/user/emails')
|
||||||
|
expect(response.statusCode).to.equal(200)
|
||||||
|
const fullEmails = JSON.parse(response.body)
|
||||||
|
expect(fullEmails.length).to.equal(4)
|
||||||
|
expect(fullEmails[0].affiliation).to.not.exist
|
||||||
|
expect(fullEmails[2].reconfirmedAt).to.not.exist
|
||||||
|
expect(
|
||||||
|
fullEmails[1].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
expect(
|
||||||
|
fullEmails[2].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
expect(
|
||||||
|
fullEmails[3].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,7 +4,9 @@ const Settings = require('settings-sharelatex')
|
||||||
const UserCreator = require('../../../../app/src/Features/User/UserCreator')
|
const UserCreator = require('../../../../app/src/Features/User/UserCreator')
|
||||||
const UserGetter = require('../../../../app/src/Features/User/UserGetter')
|
const UserGetter = require('../../../../app/src/Features/User/UserGetter')
|
||||||
const UserUpdater = require('../../../../app/src/Features/User/UserUpdater')
|
const UserUpdater = require('../../../../app/src/Features/User/UserUpdater')
|
||||||
|
const moment = require('moment')
|
||||||
const request = require('request-promise-native')
|
const request = require('request-promise-native')
|
||||||
|
const { db } = require('../../../../app/src/infrastructure/mongodb')
|
||||||
const { ObjectId } = require('mongodb')
|
const { ObjectId } = require('mongodb')
|
||||||
|
|
||||||
let globalUserNum = 1
|
let globalUserNum = 1
|
||||||
|
@ -289,6 +291,76 @@ class UserHelper {
|
||||||
|
|
||||||
return userHelper
|
return userHelper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addEmailAndConfirm(userId, email) {
|
||||||
|
let response = await this.request.post({
|
||||||
|
form: {
|
||||||
|
email
|
||||||
|
},
|
||||||
|
simple: false,
|
||||||
|
uri: '/user/emails'
|
||||||
|
})
|
||||||
|
expect(response.statusCode).to.equal(204)
|
||||||
|
const token = (
|
||||||
|
await db.tokens.findOne({
|
||||||
|
'data.user_id': userId.toString(),
|
||||||
|
'data.email': email
|
||||||
|
})
|
||||||
|
).token
|
||||||
|
response = await this.request.post({
|
||||||
|
form: {
|
||||||
|
token
|
||||||
|
},
|
||||||
|
simple: false,
|
||||||
|
uri: '/user/emails/confirm'
|
||||||
|
})
|
||||||
|
expect(response.statusCode).to.equal(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
async backdateConfirmation(userId, email, days) {
|
||||||
|
const confirmedDate = moment()
|
||||||
|
.subtract(days, 'days')
|
||||||
|
.toDate()
|
||||||
|
const query = {
|
||||||
|
_id: userId,
|
||||||
|
'emails.email': email
|
||||||
|
}
|
||||||
|
const update = {
|
||||||
|
$set: {
|
||||||
|
'emails.$.confirmedAt': confirmedDate,
|
||||||
|
'emails.$.reconfirmedAt': confirmedDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await UserUpdater.promises.updateUser(query, update)
|
||||||
|
}
|
||||||
|
|
||||||
|
async confirmEmail(userId, email) {
|
||||||
|
let response
|
||||||
|
// UserHelper.createUser does not create a confirmation token
|
||||||
|
response = await this.request.post({
|
||||||
|
form: {
|
||||||
|
email
|
||||||
|
},
|
||||||
|
simple: false,
|
||||||
|
uri: '/user/emails/resend_confirmation'
|
||||||
|
})
|
||||||
|
expect(response.statusCode).to.equal(200)
|
||||||
|
const tokenData = await db.tokens
|
||||||
|
.find({
|
||||||
|
use: 'email_confirmation',
|
||||||
|
'data.user_id': userId.toString(),
|
||||||
|
usedAt: { $exists: false }
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
response = await this.request.post({
|
||||||
|
form: {
|
||||||
|
token: tokenData.token
|
||||||
|
},
|
||||||
|
simple: false,
|
||||||
|
uri: '/user/emails/confirm'
|
||||||
|
})
|
||||||
|
expect(response.statusCode).to.equal(200)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = UserHelper
|
module.exports = UserHelper
|
||||||
|
|
|
@ -2,6 +2,7 @@ const { ObjectId } = require('mongodb')
|
||||||
const should = require('chai').should()
|
const should = require('chai').should()
|
||||||
const SandboxedModule = require('sandboxed-module')
|
const SandboxedModule = require('sandboxed-module')
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
|
const moment = require('moment')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const sinon = require('sinon')
|
const sinon = require('sinon')
|
||||||
const modulePath = path.join(
|
const modulePath = path.join(
|
||||||
|
@ -41,7 +42,6 @@ describe('UserGetter', function() {
|
||||||
},
|
},
|
||||||
ObjectId
|
ObjectId
|
||||||
}
|
}
|
||||||
const settings = { apis: { v1: { url: 'v1.url', user: '', pass: '' } } }
|
|
||||||
this.getUserAffiliations = sinon.stub().resolves([])
|
this.getUserAffiliations = sinon.stub().resolves([])
|
||||||
|
|
||||||
this.UserGetter = SandboxedModule.require(modulePath, {
|
this.UserGetter = SandboxedModule.require(modulePath, {
|
||||||
|
@ -57,7 +57,9 @@ describe('UserGetter', function() {
|
||||||
'@overleaf/metrics': {
|
'@overleaf/metrics': {
|
||||||
timeAsyncMethod: sinon.stub()
|
timeAsyncMethod: sinon.stub()
|
||||||
},
|
},
|
||||||
'settings-sharelatex': settings,
|
'settings-sharelatex': (this.settings = {
|
||||||
|
reconfirmNotificationDays: 14
|
||||||
|
}),
|
||||||
'../Institutions/InstitutionsAPI': {
|
'../Institutions/InstitutionsAPI': {
|
||||||
promises: {
|
promises: {
|
||||||
getUserAffiliations: this.getUserAffiliations
|
getUserAffiliations: this.getUserAffiliations
|
||||||
|
@ -192,7 +194,8 @@ describe('UserGetter', function() {
|
||||||
inferred: affiliationsData[0].inferred,
|
inferred: affiliationsData[0].inferred,
|
||||||
department: affiliationsData[0].department,
|
department: affiliationsData[0].department,
|
||||||
role: affiliationsData[0].role,
|
role: affiliationsData[0].role,
|
||||||
licence: affiliationsData[0].licence
|
licence: affiliationsData[0].licence,
|
||||||
|
inReconfirmNotificationPeriod: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -262,6 +265,528 @@ describe('UserGetter', function() {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('affiliation reconfirmation', function() {
|
||||||
|
const institutionNonSSO = {
|
||||||
|
id: 1,
|
||||||
|
name: 'University Name',
|
||||||
|
commonsAccount: true,
|
||||||
|
isUniversity: true,
|
||||||
|
confirmed: true,
|
||||||
|
ssoBeta: false,
|
||||||
|
ssoEnabled: false,
|
||||||
|
maxConfirmationMonths: 12
|
||||||
|
}
|
||||||
|
const institutionSSO = {
|
||||||
|
id: 2,
|
||||||
|
name: 'SSO University Name',
|
||||||
|
isUniversity: true,
|
||||||
|
confirmed: true,
|
||||||
|
ssoBeta: false,
|
||||||
|
ssoEnabled: true,
|
||||||
|
maxConfirmationMonths: 12
|
||||||
|
}
|
||||||
|
describe('non-SSO institutions', function() {
|
||||||
|
const email1 = 'leonard@example-affiliation.com'
|
||||||
|
const email2 = 'mccoy@example-affiliation.com'
|
||||||
|
const affiliationsData = [
|
||||||
|
{
|
||||||
|
email: email1,
|
||||||
|
role: 'Prof',
|
||||||
|
department: 'Medicine',
|
||||||
|
inferred: false,
|
||||||
|
licence: 'pro_plus',
|
||||||
|
institution: institutionNonSSO
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email2,
|
||||||
|
role: 'Prof',
|
||||||
|
department: 'Medicine',
|
||||||
|
inferred: false,
|
||||||
|
licence: 'pro_plus',
|
||||||
|
institution: institutionNonSSO
|
||||||
|
}
|
||||||
|
]
|
||||||
|
it('should flag inReconfirmNotificationPeriod for all affiliations in period', function(done) {
|
||||||
|
const user = {
|
||||||
|
_id: '12390i',
|
||||||
|
email: email1,
|
||||||
|
emails: [
|
||||||
|
{
|
||||||
|
email: email1,
|
||||||
|
reversedHostname: 'moc.noitailiffa-elpmaxe',
|
||||||
|
confirmedAt: moment()
|
||||||
|
.subtract(
|
||||||
|
institutionNonSSO.maxConfirmationMonths + 2,
|
||||||
|
'months'
|
||||||
|
)
|
||||||
|
.toDate(),
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email2,
|
||||||
|
reversedHostname: 'moc.noitailiffa-elpmaxe',
|
||||||
|
confirmedAt: moment()
|
||||||
|
.subtract(
|
||||||
|
institutionNonSSO.maxConfirmationMonths + 1,
|
||||||
|
'months'
|
||||||
|
)
|
||||||
|
.toDate()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.getUserAffiliations.resolves(affiliationsData)
|
||||||
|
this.UserGetter.promises.getUser = sinon.stub().resolves(user)
|
||||||
|
this.UserGetter.getUserFullEmails(
|
||||||
|
this.fakeUser._id,
|
||||||
|
(error, fullEmails) => {
|
||||||
|
expect(error).to.not.exist
|
||||||
|
expect(
|
||||||
|
fullEmails[0].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
expect(
|
||||||
|
fullEmails[1].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('should not flag affiliations outside of notification period', function(done) {
|
||||||
|
const aboutToBeWithinPeriod = moment()
|
||||||
|
.subtract(institutionNonSSO.maxConfirmationMonths, 'months')
|
||||||
|
.add(15, 'days')
|
||||||
|
.toDate() // expires in 15 days
|
||||||
|
const user = {
|
||||||
|
_id: '12390i',
|
||||||
|
email: email1,
|
||||||
|
emails: [
|
||||||
|
{
|
||||||
|
email: email1,
|
||||||
|
reversedHostname: 'moc.noitailiffa-elpmaxe',
|
||||||
|
confirmedAt: new Date(),
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email2,
|
||||||
|
reversedHostname: 'moc.noitailiffa-elpmaxe',
|
||||||
|
confirmedAt: aboutToBeWithinPeriod
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.getUserAffiliations.resolves(affiliationsData)
|
||||||
|
this.UserGetter.promises.getUser = sinon.stub().resolves(user)
|
||||||
|
this.UserGetter.getUserFullEmails(
|
||||||
|
this.fakeUser._id,
|
||||||
|
(error, fullEmails) => {
|
||||||
|
expect(error).to.not.exist
|
||||||
|
expect(
|
||||||
|
fullEmails[0].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(false)
|
||||||
|
expect(
|
||||||
|
fullEmails[1].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(false)
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('SSO institutions', function() {
|
||||||
|
it('should flag only linked email, if in notification period', function(done) {
|
||||||
|
const email1 = 'email1@sso.bar'
|
||||||
|
const email2 = 'email2@sso.bar'
|
||||||
|
const email3 = 'email3@sso.bar'
|
||||||
|
|
||||||
|
const affiliationsData = [
|
||||||
|
{
|
||||||
|
email: email1,
|
||||||
|
role: 'Prof',
|
||||||
|
department: 'Maths',
|
||||||
|
inferred: false,
|
||||||
|
licence: 'pro_plus',
|
||||||
|
institution: institutionSSO
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email2,
|
||||||
|
role: 'Prof',
|
||||||
|
department: 'Maths',
|
||||||
|
inferred: false,
|
||||||
|
licence: 'pro_plus',
|
||||||
|
institution: institutionSSO
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email3,
|
||||||
|
role: 'Prof',
|
||||||
|
department: 'Maths',
|
||||||
|
inferred: false,
|
||||||
|
licence: 'pro_plus',
|
||||||
|
institution: institutionSSO
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const user = {
|
||||||
|
_id: '12390i',
|
||||||
|
email: email1,
|
||||||
|
emails: [
|
||||||
|
{
|
||||||
|
email: email1,
|
||||||
|
reversedHostname: 'rab.oss',
|
||||||
|
confirmedAt: new Date('2019-09-24'),
|
||||||
|
reconfirmedAt: new Date('2019-09-24'),
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email2,
|
||||||
|
reversedHostname: 'rab.oss',
|
||||||
|
confirmedAt: new Date('2019-09-24'),
|
||||||
|
reconfirmedAt: new Date('2019-09-24'),
|
||||||
|
samlProviderId: institutionSSO.id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email3,
|
||||||
|
reversedHostname: 'rab.oss',
|
||||||
|
confirmedAt: new Date('2019-09-24'),
|
||||||
|
reconfirmedAt: new Date('2019-09-24')
|
||||||
|
}
|
||||||
|
],
|
||||||
|
samlIdentifiers: [
|
||||||
|
{
|
||||||
|
providerId: institutionSSO.id,
|
||||||
|
externalUserId: 'abc123'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.getUserAffiliations.resolves(affiliationsData)
|
||||||
|
this.UserGetter.promises.getUser = sinon.stub().resolves(user)
|
||||||
|
this.UserGetter.getUserFullEmails(
|
||||||
|
this.fakeUser._id,
|
||||||
|
(error, fullEmails) => {
|
||||||
|
expect(error).to.not.exist
|
||||||
|
expect(
|
||||||
|
fullEmails[0].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(false)
|
||||||
|
expect(
|
||||||
|
fullEmails[1].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
expect(
|
||||||
|
fullEmails[2].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(false)
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('multiple institution affiliations', function() {
|
||||||
|
it('should flag each institution', function(done) {
|
||||||
|
const email1 = 'email1@sso.bar'
|
||||||
|
const email2 = 'email2@sso.bar'
|
||||||
|
const email3 = 'email3@foo.bar'
|
||||||
|
const email4 = 'email4@foo.bar'
|
||||||
|
|
||||||
|
const affiliationsData = [
|
||||||
|
{
|
||||||
|
email: email1,
|
||||||
|
role: 'Prof',
|
||||||
|
department: 'Maths',
|
||||||
|
inferred: false,
|
||||||
|
licence: 'pro_plus',
|
||||||
|
institution: institutionSSO
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email2,
|
||||||
|
role: 'Prof',
|
||||||
|
department: 'Maths',
|
||||||
|
inferred: false,
|
||||||
|
licence: 'pro_plus',
|
||||||
|
institution: institutionSSO
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email3,
|
||||||
|
role: 'Prof',
|
||||||
|
department: 'Maths',
|
||||||
|
inferred: false,
|
||||||
|
licence: 'pro_plus',
|
||||||
|
institution: institutionNonSSO
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email4,
|
||||||
|
role: 'Prof',
|
||||||
|
department: 'Maths',
|
||||||
|
inferred: false,
|
||||||
|
licence: 'pro_plus',
|
||||||
|
institution: institutionNonSSO
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const user = {
|
||||||
|
_id: '12390i',
|
||||||
|
email: email1,
|
||||||
|
emails: [
|
||||||
|
{
|
||||||
|
email: email1,
|
||||||
|
reversedHostname: 'rab.oss',
|
||||||
|
confirmedAt: '2019-09-24T20:25:08.503Z',
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email2,
|
||||||
|
reversedHostname: 'rab.oss',
|
||||||
|
confirmedAt: new Date('2019-09-24T20:25:08.503Z'),
|
||||||
|
samlProviderId: institutionSSO.id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email3,
|
||||||
|
reversedHostname: 'rab.oof',
|
||||||
|
confirmedAt: new Date('2019-10-24T20:25:08.503Z')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email4,
|
||||||
|
reversedHostname: 'rab.oof',
|
||||||
|
confirmedAt: new Date('2019-09-24T20:25:08.503Z')
|
||||||
|
}
|
||||||
|
],
|
||||||
|
samlIdentifiers: [
|
||||||
|
{
|
||||||
|
providerId: institutionSSO.id,
|
||||||
|
externalUserId: 'abc123'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.getUserAffiliations.resolves(affiliationsData)
|
||||||
|
this.UserGetter.promises.getUser = sinon.stub().resolves(user)
|
||||||
|
this.UserGetter.getUserFullEmails(
|
||||||
|
this.fakeUser._id,
|
||||||
|
(error, fullEmails) => {
|
||||||
|
expect(error).to.not.exist
|
||||||
|
expect(
|
||||||
|
fullEmails[0].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.to.equal(false)
|
||||||
|
expect(
|
||||||
|
fullEmails[1].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
expect(
|
||||||
|
fullEmails[2].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
expect(
|
||||||
|
fullEmails[3].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('reconfirmedAt', function() {
|
||||||
|
it('only use confirmedAt when no reconfirmedAt', function(done) {
|
||||||
|
const email1 = 'email1@foo.bar'
|
||||||
|
const email2 = 'email2@foo.bar'
|
||||||
|
const email3 = 'email3@foo.bar'
|
||||||
|
|
||||||
|
const affiliationsData = [
|
||||||
|
{
|
||||||
|
email: email1,
|
||||||
|
role: 'Prof',
|
||||||
|
department: 'Maths',
|
||||||
|
inferred: false,
|
||||||
|
licence: 'pro_plus',
|
||||||
|
institution: institutionNonSSO
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email2,
|
||||||
|
role: 'Prof',
|
||||||
|
department: 'Maths',
|
||||||
|
inferred: false,
|
||||||
|
licence: 'pro_plus',
|
||||||
|
institution: institutionNonSSO
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email3,
|
||||||
|
role: 'Prof',
|
||||||
|
department: 'Maths',
|
||||||
|
inferred: false,
|
||||||
|
licence: 'pro_plus',
|
||||||
|
institution: institutionNonSSO
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const user = {
|
||||||
|
_id: '12390i',
|
||||||
|
email: email1,
|
||||||
|
emails: [
|
||||||
|
{
|
||||||
|
email: email1,
|
||||||
|
reversedHostname: 'rab.oof',
|
||||||
|
confirmedAt: moment().subtract(
|
||||||
|
institutionSSO.maxConfirmationMonths * 2,
|
||||||
|
'months'
|
||||||
|
),
|
||||||
|
reconfirmedAt: moment().subtract(
|
||||||
|
institutionSSO.maxConfirmationMonths * 3,
|
||||||
|
'months'
|
||||||
|
),
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email2,
|
||||||
|
reversedHostname: 'rab.oof',
|
||||||
|
confirmedAt: moment().subtract(
|
||||||
|
institutionSSO.maxConfirmationMonths * 3,
|
||||||
|
'months'
|
||||||
|
),
|
||||||
|
reconfirmedAt: moment().subtract(
|
||||||
|
institutionSSO.maxConfirmationMonths * 2,
|
||||||
|
'months'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email3,
|
||||||
|
reversedHostname: 'rab.oof',
|
||||||
|
confirmedAt: moment().subtract(
|
||||||
|
institutionSSO.maxConfirmationMonths * 4,
|
||||||
|
'months'
|
||||||
|
),
|
||||||
|
reconfirmedAt: moment().subtract(
|
||||||
|
institutionSSO.maxConfirmationMonths * 4,
|
||||||
|
'months'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.getUserAffiliations.resolves(affiliationsData)
|
||||||
|
this.UserGetter.promises.getUser = sinon.stub().resolves(user)
|
||||||
|
this.UserGetter.getUserFullEmails(
|
||||||
|
this.fakeUser._id,
|
||||||
|
(error, fullEmails) => {
|
||||||
|
expect(error).to.not.exist
|
||||||
|
expect(
|
||||||
|
fullEmails[0].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
expect(
|
||||||
|
fullEmails[1].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
expect(
|
||||||
|
fullEmails[2].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('before reconfirmation period expires and within reconfirmation notification period', function() {
|
||||||
|
const email = 'leonard@example-affiliation.com'
|
||||||
|
it('should flag the email', function(done) {
|
||||||
|
const confirmedAt = moment()
|
||||||
|
.subtract(institutionNonSSO.maxConfirmationMonths, 'months')
|
||||||
|
.subtract(14, 'days')
|
||||||
|
.toDate() // expires in 14 days
|
||||||
|
const affiliationsData = [
|
||||||
|
{
|
||||||
|
email,
|
||||||
|
role: 'Prof',
|
||||||
|
department: 'Medicine',
|
||||||
|
inferred: false,
|
||||||
|
licence: 'pro_plus',
|
||||||
|
institution: institutionNonSSO
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const user = {
|
||||||
|
_id: '12390i',
|
||||||
|
email,
|
||||||
|
emails: [
|
||||||
|
{
|
||||||
|
email,
|
||||||
|
confirmedAt,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.getUserAffiliations.resolves(affiliationsData)
|
||||||
|
this.UserGetter.promises.getUser = sinon.stub().resolves(user)
|
||||||
|
this.UserGetter.getUserFullEmails(
|
||||||
|
this.fakeUser._id,
|
||||||
|
(error, fullEmails) => {
|
||||||
|
expect(error).to.not.exist
|
||||||
|
expect(
|
||||||
|
fullEmails[0].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(true)
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when no Settings.reconfirmNotificationDays', function() {
|
||||||
|
it('should always return inReconfirmNotificationPeriod:false', function(done) {
|
||||||
|
const email1 = 'email1@sso.bar'
|
||||||
|
const email2 = 'email2@foo.bar'
|
||||||
|
const email3 = 'email3@foo.bar'
|
||||||
|
const confirmedAtAboutToExpire = moment()
|
||||||
|
.subtract(institutionNonSSO.maxConfirmationMonths, 'months')
|
||||||
|
.subtract(14, 'days')
|
||||||
|
.toDate() // expires in 14 days
|
||||||
|
|
||||||
|
const affiliationsData = [
|
||||||
|
{
|
||||||
|
email: email1,
|
||||||
|
institution: institutionSSO
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email2,
|
||||||
|
institution: institutionNonSSO
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email3,
|
||||||
|
institution: institutionNonSSO
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const user = {
|
||||||
|
_id: '12390i',
|
||||||
|
email: email1,
|
||||||
|
emails: [
|
||||||
|
{
|
||||||
|
email: email1,
|
||||||
|
confirmedAt: confirmedAtAboutToExpire,
|
||||||
|
default: true,
|
||||||
|
samlProviderId: institutionSSO.id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email2,
|
||||||
|
confirmedAt: new Date('2019-09-24T20:25:08.503Z')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: email3,
|
||||||
|
confirmedAt: new Date('2019-10-24T20:25:08.503Z')
|
||||||
|
}
|
||||||
|
],
|
||||||
|
samlIdentifiers: [
|
||||||
|
{
|
||||||
|
providerId: institutionSSO.id,
|
||||||
|
externalUserId: 'abc123'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.settings.reconfirmNotificationDays = undefined
|
||||||
|
this.getUserAffiliations.resolves(affiliationsData)
|
||||||
|
this.UserGetter.promises.getUser = sinon.stub().resolves(user)
|
||||||
|
this.UserGetter.getUserFullEmails(
|
||||||
|
this.fakeUser._id,
|
||||||
|
(error, fullEmails) => {
|
||||||
|
expect(error).to.not.exist
|
||||||
|
expect(
|
||||||
|
fullEmails[0].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.to.equal(false)
|
||||||
|
expect(
|
||||||
|
fullEmails[1].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(false)
|
||||||
|
expect(
|
||||||
|
fullEmails[2].affiliation.inReconfirmNotificationPeriod
|
||||||
|
).to.equal(false)
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getUserbyMainEmail', function() {
|
describe('getUserbyMainEmail', function() {
|
||||||
|
|
Loading…
Reference in a new issue