mirror of
https://github.com/overleaf/overleaf.git
synced 2025-01-27 06:53:55 +00:00
Merge pull request #2840 from overleaf/jel-sso-redundant-subscription-notification
Redundant subscription notification if entitlement via SSO GitOrigin-RevId: 8529204e78c3a43d87acbb375fea15c62cad48a3
This commit is contained in:
parent
a0809e1d85
commit
00bdc52fab
3 changed files with 266 additions and 166 deletions
|
@ -11,177 +11,196 @@
|
|||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const NotificationsHandler = require('./NotificationsHandler')
|
||||
const { promisifyAll } = require('../../util/promises')
|
||||
const request = require('request')
|
||||
const settings = require('settings-sharelatex')
|
||||
|
||||
module.exports = {
|
||||
// Note: notification keys should be url-safe
|
||||
|
||||
featuresUpgradedByAffiliation(affiliation, user) {
|
||||
return {
|
||||
key: `features-updated-by=${affiliation.institutionId}`,
|
||||
create(callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
const messageOpts = { institutionName: affiliation.institutionName }
|
||||
return NotificationsHandler.createNotification(
|
||||
user._id,
|
||||
this.key,
|
||||
'notification_features_upgraded_by_affiliation',
|
||||
messageOpts,
|
||||
null,
|
||||
false,
|
||||
callback
|
||||
)
|
||||
},
|
||||
read(callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
return NotificationsHandler.markAsReadByKeyOnly(this.key, callback)
|
||||
function featuresUpgradedByAffiliation(affiliation, user) {
|
||||
return {
|
||||
key: `features-updated-by=${affiliation.institutionId}`,
|
||||
create(callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
redundantPersonalSubscription(affiliation, user) {
|
||||
return {
|
||||
key: `redundant-personal-subscription-${affiliation.institutionId}`,
|
||||
create(callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
const messageOpts = { institutionName: affiliation.institutionName }
|
||||
return NotificationsHandler.createNotification(
|
||||
user._id,
|
||||
this.key,
|
||||
'notification_personal_subscription_not_required_due_to_affiliation',
|
||||
messageOpts,
|
||||
null,
|
||||
false,
|
||||
callback
|
||||
)
|
||||
},
|
||||
read(callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
return NotificationsHandler.markAsReadByKeyOnly(this.key, callback)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
projectInvite(invite, project, sendingUser, user) {
|
||||
return {
|
||||
key: `project-invite-${invite._id}`,
|
||||
create(callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
const messageOpts = {
|
||||
userName: sendingUser.first_name,
|
||||
projectName: project.name,
|
||||
projectId: project._id.toString(),
|
||||
token: invite.token
|
||||
}
|
||||
return NotificationsHandler.createNotification(
|
||||
user._id,
|
||||
this.key,
|
||||
'notification_project_invite',
|
||||
messageOpts,
|
||||
invite.expires,
|
||||
callback
|
||||
)
|
||||
},
|
||||
read(callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
return NotificationsHandler.markAsReadByKeyOnly(this.key, callback)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
ipMatcherAffiliation(userId) {
|
||||
return {
|
||||
create(ip, callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
if (!settings.apis.v1.url) {
|
||||
return null
|
||||
} // service is not configured
|
||||
return request(
|
||||
{
|
||||
method: 'GET',
|
||||
url: `${settings.apis.v1.url}/api/v2/users/${userId}/ip_matcher`,
|
||||
auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass },
|
||||
body: { ip },
|
||||
json: true,
|
||||
timeout: 20 * 1000
|
||||
},
|
||||
function(error, response, body) {
|
||||
if (error != null) {
|
||||
return error
|
||||
}
|
||||
if (response.statusCode !== 200) {
|
||||
return null
|
||||
}
|
||||
|
||||
const key = `ip-matched-affiliation-${body.id}`
|
||||
const messageOpts = {
|
||||
university_name: body.name,
|
||||
content: body.enrolment_ad_html
|
||||
}
|
||||
return NotificationsHandler.createNotification(
|
||||
userId,
|
||||
key,
|
||||
'notification_ip_matched_affiliation',
|
||||
messageOpts,
|
||||
null,
|
||||
false,
|
||||
callback
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
read(university_id, callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
const key = `ip-matched-affiliation-${university_id}`
|
||||
return NotificationsHandler.markAsReadWithKey(userId, key, callback)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
tpdsFileLimit(user_id) {
|
||||
return {
|
||||
key: `tpdsFileLimit-${user_id}`,
|
||||
create(projectName, callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
const messageOpts = {
|
||||
projectName: projectName
|
||||
}
|
||||
return NotificationsHandler.createNotification(
|
||||
user_id,
|
||||
this.key,
|
||||
'notification_tpds_file_limit',
|
||||
messageOpts,
|
||||
null,
|
||||
true,
|
||||
callback
|
||||
)
|
||||
},
|
||||
read(callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
return NotificationsHandler.markAsReadByKeyOnly(this.key, callback)
|
||||
const messageOpts = { institutionName: affiliation.institutionName }
|
||||
return NotificationsHandler.createNotification(
|
||||
user._id,
|
||||
this.key,
|
||||
'notification_features_upgraded_by_affiliation',
|
||||
messageOpts,
|
||||
null,
|
||||
false,
|
||||
callback
|
||||
)
|
||||
},
|
||||
read(callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
return NotificationsHandler.markAsReadByKeyOnly(this.key, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function redundantPersonalSubscription(affiliation, user) {
|
||||
return {
|
||||
key: `redundant-personal-subscription-${affiliation.institutionId}`,
|
||||
create(callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
const messageOpts = { institutionName: affiliation.institutionName }
|
||||
return NotificationsHandler.createNotification(
|
||||
user._id,
|
||||
this.key,
|
||||
'notification_personal_subscription_not_required_due_to_affiliation',
|
||||
messageOpts,
|
||||
null,
|
||||
false,
|
||||
callback
|
||||
)
|
||||
},
|
||||
read(callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
return NotificationsHandler.markAsReadByKeyOnly(this.key, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function projectInvite(invite, project, sendingUser, user) {
|
||||
return {
|
||||
key: `project-invite-${invite._id}`,
|
||||
create(callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
const messageOpts = {
|
||||
userName: sendingUser.first_name,
|
||||
projectName: project.name,
|
||||
projectId: project._id.toString(),
|
||||
token: invite.token
|
||||
}
|
||||
return NotificationsHandler.createNotification(
|
||||
user._id,
|
||||
this.key,
|
||||
'notification_project_invite',
|
||||
messageOpts,
|
||||
invite.expires,
|
||||
callback
|
||||
)
|
||||
},
|
||||
read(callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
return NotificationsHandler.markAsReadByKeyOnly(this.key, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ipMatcherAffiliation(userId) {
|
||||
return {
|
||||
create(ip, callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
if (!settings.apis.v1.url) {
|
||||
return null
|
||||
} // service is not configured
|
||||
return request(
|
||||
{
|
||||
method: 'GET',
|
||||
url: `${settings.apis.v1.url}/api/v2/users/${userId}/ip_matcher`,
|
||||
auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass },
|
||||
body: { ip },
|
||||
json: true,
|
||||
timeout: 20 * 1000
|
||||
},
|
||||
function(error, response, body) {
|
||||
if (error != null) {
|
||||
return error
|
||||
}
|
||||
if (response.statusCode !== 200) {
|
||||
return null
|
||||
}
|
||||
|
||||
const key = `ip-matched-affiliation-${body.id}`
|
||||
const messageOpts = {
|
||||
university_name: body.name,
|
||||
content: body.enrolment_ad_html
|
||||
}
|
||||
return NotificationsHandler.createNotification(
|
||||
userId,
|
||||
key,
|
||||
'notification_ip_matched_affiliation',
|
||||
messageOpts,
|
||||
null,
|
||||
false,
|
||||
callback
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
read(university_id, callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
const key = `ip-matched-affiliation-${university_id}`
|
||||
return NotificationsHandler.markAsReadWithKey(userId, key, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tpdsFileLimit(user_id) {
|
||||
return {
|
||||
key: `tpdsFileLimit-${user_id}`,
|
||||
create(projectName, callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
const messageOpts = {
|
||||
projectName: projectName
|
||||
}
|
||||
return NotificationsHandler.createNotification(
|
||||
user_id,
|
||||
this.key,
|
||||
'notification_tpds_file_limit',
|
||||
messageOpts,
|
||||
null,
|
||||
true,
|
||||
callback
|
||||
)
|
||||
},
|
||||
read(callback) {
|
||||
if (callback == null) {
|
||||
callback = function() {}
|
||||
}
|
||||
return NotificationsHandler.markAsReadByKeyOnly(this.key, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const NotificationsBuilder = {
|
||||
// Note: notification keys should be url-safe
|
||||
|
||||
featuresUpgradedByAffiliation,
|
||||
|
||||
redundantPersonalSubscription,
|
||||
|
||||
projectInvite,
|
||||
|
||||
ipMatcherAffiliation,
|
||||
|
||||
tpdsFileLimit
|
||||
}
|
||||
|
||||
NotificationsBuilder.promises = {
|
||||
redundantPersonalSubscription: function(affiliation, user) {
|
||||
return promisifyAll(redundantPersonalSubscription(affiliation, user))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NotificationsBuilder
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
const EmailHandler = require('../Email/EmailHandler')
|
||||
const Errors = require('../Errors/Errors')
|
||||
const InstitutionsAPI = require('../Institutions/InstitutionsAPI')
|
||||
const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
|
||||
const OError = require('@overleaf/o-error')
|
||||
const SubscriptionLocator = require('../Subscription/SubscriptionLocator')
|
||||
const UserGetter = require('../User/UserGetter')
|
||||
const UserUpdater = require('../User/UserUpdater')
|
||||
const logger = require('logger-sharelatex')
|
||||
|
@ -151,6 +153,24 @@ async function getUser(providerId, externalUserId) {
|
|||
return user
|
||||
}
|
||||
|
||||
async function redundantSubscription(userId, providerId, providerName) {
|
||||
const subscription = await SubscriptionLocator.promises.getUserIndividualSubscription(
|
||||
userId
|
||||
)
|
||||
|
||||
if (subscription) {
|
||||
await NotificationsBuilder.promises
|
||||
.redundantPersonalSubscription(
|
||||
{
|
||||
institutionId: providerId,
|
||||
institutionName: providerName
|
||||
},
|
||||
{ _id: userId }
|
||||
)
|
||||
.create()
|
||||
}
|
||||
}
|
||||
|
||||
async function linkAccounts(
|
||||
userId,
|
||||
externalUserId,
|
||||
|
@ -171,6 +191,11 @@ async function linkAccounts(
|
|||
// update v1 affiliations record
|
||||
if (hasEntitlement) {
|
||||
await InstitutionsAPI.promises.addEntitlement(userId, institutionEmail)
|
||||
try {
|
||||
await redundantSubscription(userId, providerId, providerName)
|
||||
} catch (error) {
|
||||
logger.err({ err: error }, 'error checking redundant subscription')
|
||||
}
|
||||
} else {
|
||||
await InstitutionsAPI.promises.removeEntitlement(userId, institutionEmail)
|
||||
}
|
||||
|
@ -271,6 +296,7 @@ const SAMLIdentityManager = {
|
|||
entitlementAttributeMatches,
|
||||
getUser,
|
||||
linkAccounts,
|
||||
redundantSubscription,
|
||||
unlinkAccounts,
|
||||
updateEntitlement,
|
||||
userHasEntitlement
|
||||
|
|
|
@ -49,6 +49,18 @@ describe('SAMLIdentityManager', function() {
|
|||
sendEmail: sinon.stub().yields()
|
||||
}),
|
||||
'../Errors/Errors': this.Errors,
|
||||
'../Notifications/NotificationsBuilder': (this.NotificationsBuilder = {
|
||||
promises: {
|
||||
redundantPersonalSubscription: sinon
|
||||
.stub()
|
||||
.returns({ create: sinon.stub().resolves() })
|
||||
}
|
||||
}),
|
||||
'../Subscription/SubscriptionLocator': (this.SubscriptionLocator = {
|
||||
promises: {
|
||||
getUserIndividualSubscription: sinon.stub().resolves()
|
||||
}
|
||||
}),
|
||||
'../../models/User': {
|
||||
User: (this.User = {
|
||||
findOneAndUpdate: sinon.stub(),
|
||||
|
@ -212,4 +224,47 @@ describe('SAMLIdentityManager', function() {
|
|||
).should.equal(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('redundantSubscription', function() {
|
||||
const userId = '1bv'
|
||||
const providerId = 123
|
||||
const providerName = 'University Name'
|
||||
describe('with a personal subscription', function() {
|
||||
beforeEach(function() {
|
||||
this.SubscriptionLocator.promises.getUserIndividualSubscription.resolves(
|
||||
{
|
||||
planCode: 'professional'
|
||||
}
|
||||
)
|
||||
})
|
||||
it('should create redundant personal subscription notification ', async function() {
|
||||
try {
|
||||
await this.SAMLIdentityManager.redundantSubscription(
|
||||
userId,
|
||||
providerId,
|
||||
providerName
|
||||
)
|
||||
} catch (error) {
|
||||
expect(error).to.not.exist
|
||||
}
|
||||
expect(this.NotificationsBuilder.promises.redundantPersonalSubscription)
|
||||
.to.have.been.calledOnce
|
||||
})
|
||||
})
|
||||
describe('without a personal subscription', function() {
|
||||
it('should create redundant personal subscription notification ', async function() {
|
||||
try {
|
||||
await this.SAMLIdentityManager.redundantSubscription(
|
||||
userId,
|
||||
providerId,
|
||||
providerName
|
||||
)
|
||||
} catch (error) {
|
||||
expect(error).to.not.exist
|
||||
}
|
||||
expect(this.NotificationsBuilder.promises.redundantPersonalSubscription)
|
||||
.to.not.have.been.called
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue