add saml acceptance tests. get/set entitlement.

GitOrigin-RevId: 65721aadb91678eafaf5a214a2921fe3dd276efa
This commit is contained in:
Ersun Warncke 2019-10-05 13:43:21 -04:00 committed by sharelatex
parent de8ac8ace7
commit 849f21fde3
6 changed files with 230 additions and 25 deletions

View file

@ -23,6 +23,7 @@ const logger = require('logger-sharelatex')
const ReferalFeatures = require('../Referal/ReferalFeatures')
const V1SubscriptionManager = require('./V1SubscriptionManager')
const InstitutionsFeatures = require('../Institutions/InstitutionsFeatures')
const UserGetter = require('../User/UserGetter')
const oneMonthInSeconds = 60 * 60 * 24 * 30
@ -47,6 +48,9 @@ module.exports = FeaturesUpdater = {
},
bonusFeatures(cb) {
return ReferalFeatures.getBonusFeatures(user_id, cb)
},
samlFeatures(cb) {
return FeaturesUpdater._getSamlFeatures(user_id, cb)
}
}
return async.series(jobs, function(err, results) {
@ -63,7 +67,8 @@ module.exports = FeaturesUpdater = {
groupFeatureSets,
institutionFeatures,
v1Features,
bonusFeatures
bonusFeatures,
samlFeatures
} = results
logger.log(
{
@ -72,7 +77,8 @@ module.exports = FeaturesUpdater = {
groupFeatureSets,
institutionFeatures,
v1Features,
bonusFeatures
bonusFeatures,
samlFeatures
},
'merging user features'
)
@ -80,7 +86,8 @@ module.exports = FeaturesUpdater = {
individualFeatures,
institutionFeatures,
v1Features,
bonusFeatures
bonusFeatures,
samlFeatures
])
const features = _.reduce(
featureSets,
@ -113,6 +120,30 @@ module.exports = FeaturesUpdater = {
)
},
_getSamlFeatures(user_id, callback) {
UserGetter.getUser(user_id, (err, user) => {
if (err) {
return callback(err)
}
if (
!user ||
!Array.isArray(user.samlIdentifiers) ||
!user.samlIdentifiers.length
) {
return callback(null, {})
}
for (const samlIdentifier of user.samlIdentifiers) {
if (samlIdentifier && samlIdentifier.hasEntitlement) {
return callback(
null,
FeaturesUpdater._planCodeToFeatures('professional')
)
}
}
return callback(null, {})
})
},
_getV1Features(user_id, callback) {
if (callback == null) {
callback = function(error, features) {}

View file

@ -6,8 +6,9 @@ const { User } = require('../../models/User')
const UserGetter = require('../User/UserGetter')
const UserUpdater = require('../User/UserUpdater')
function _addIdentifier(userId, externalUserId, providerId) {
function _addIdentifier(userId, externalUserId, providerId, hasEntitlement) {
providerId = providerId.toString()
hasEntitlement = !!hasEntitlement
const query = {
_id: userId,
'samlIdentifiers.providerId': {
@ -17,6 +18,7 @@ function _addIdentifier(userId, externalUserId, providerId) {
const update = {
$push: {
samlIdentifiers: {
hasEntitlement,
externalUserId,
providerId
}
@ -113,8 +115,12 @@ function _sendUnlinkedEmail(primaryEmail, providerName) {
async function getUser(providerId, externalUserId) {
if (providerId == null || externalUserId == null) {
throw new Error('invalid arguments')
throw new Error(
`invalid arguments: providerId: ${providerId}, externalUserId: ${externalUserId}`
)
}
providerId = providerId.toString()
externalUserId = externalUserId.toString()
const query = _getUserQuery(providerId, externalUserId)
let user = await User.findOne(query).exec()
if (!user) {
@ -128,14 +134,16 @@ async function linkAccounts(
externalUserId,
institutionEmail,
providerId,
providerName
providerName,
hasEntitlement
) {
await _addIdentifier(userId, externalUserId, providerId)
await _addIdentifier(userId, externalUserId, providerId, hasEntitlement)
await _addInstitutionEmail(userId, institutionEmail, providerId)
await _sendLinkedEmail(userId, providerName)
}
async function unlinkAccounts(userId, primaryEmail, providerId, providerName) {
providerId = providerId.toString()
const query = {
_id: userId
}
@ -150,10 +158,55 @@ async function unlinkAccounts(userId, primaryEmail, providerId, providerName) {
_sendUnlinkedEmail(primaryEmail, providerName)
}
async function updateEntitlement(userId, providerId, hasEntitlement) {
providerId = providerId.toString()
hasEntitlement = !!hasEntitlement
const query = {
_id: userId,
'samlIdentifiers.providerId': providerId.toString()
}
const update = {
$set: {
'samlIdentifiers.$.hasEntitlement': hasEntitlement
}
}
await User.update(query, update).exec()
}
function entitlementAttributeMatches(entitlementAttribute, entitlementMatcher) {
if (
typeof entitlementAttribute !== 'string' ||
typeof entitlementMatcher !== 'string'
) {
return false
}
const entitlementRegExp = new RegExp(entitlementMatcher)
return !!entitlementAttribute.match(entitlementRegExp)
}
function userHasEntitlement(user, providerId) {
providerId = providerId.toString()
if (!user || !Array.isArray(user.samlIdentifiers)) {
return false
}
for (const samlIdentifier of user.samlIdentifiers) {
if (providerId && samlIdentifier.providerId !== providerId) {
continue
}
if (samlIdentifier.hasEntitlement) {
return true
}
}
return false
}
const SAMLIdentityManager = {
entitlementAttributeMatches,
getUser,
linkAccounts,
unlinkAccounts
unlinkAccounts,
updateEntitlement,
userHasEntitlement
}
module.exports = SAMLIdentityManager

View file

@ -3691,6 +3691,12 @@
}
}
},
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true
},
"boolify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/boolify/-/boolify-1.0.1.tgz",
@ -4285,6 +4291,32 @@
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
"integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII="
},
"cheerio": {
"version": "1.0.0-rc.3",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz",
"integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==",
"dev": true,
"requires": {
"css-select": "~1.2.0",
"dom-serializer": "~0.1.1",
"entities": "~1.1.1",
"htmlparser2": "^3.9.1",
"lodash": "^4.15.0",
"parse5": "^3.0.1"
},
"dependencies": {
"dom-serializer": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
"integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==",
"dev": true,
"requires": {
"domelementtype": "^1.3.0",
"entities": "^1.1.1"
}
}
}
},
"chokidar": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
@ -5270,6 +5302,36 @@
"uid-safe": "2.1.4"
}
},
"css-select": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
"integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
"dev": true,
"requires": {
"boolbase": "~1.0.0",
"css-what": "2.1",
"domutils": "1.5.1",
"nth-check": "~1.0.1"
},
"dependencies": {
"domutils": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
"dev": true,
"requires": {
"dom-serializer": "0",
"domelementtype": "1"
}
}
}
},
"css-what": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
"integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==",
"dev": true
},
"csurf": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/csurf/-/csurf-1.9.0.tgz",
@ -6285,7 +6347,6 @@
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.11.0.tgz",
"integrity": "sha512-ZnQrE/lXTTQ39ulXZ+J1DTFazV9qBy61x2bY071B+qGco8Z8q1QddsLdt/EF8Ai9hcWH72dWS0kFqXLxOxqslA==",
"dev": true,
"requires": {
"es-to-primitive": "^1.1.1",
"function-bind": "^1.1.1",
@ -6298,7 +6359,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz",
"integrity": "sha512-wXsac552n5sYhgVjyFvhXLunXZFPOiT/WgP7hFhUPU5gtaUQpm9OryPwlWQUS3Qptk8iZzY/2T3J62GtC/toSw==",
"dev": true,
"requires": {
"is-callable": "^1.1.1",
"is-date-object": "^1.0.1",
@ -10381,8 +10441,7 @@
"is-callable": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
"integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
"dev": true
"integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA=="
},
"is-ci": {
"version": "1.1.0",
@ -10401,8 +10460,7 @@
"is-date-object": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
"integrity": "sha512-P5rExV1phPi42ppoMWy7V63N3i173RY921l4JJ7zonMSxK+OWGPj76GD+cUKUb68l4vQXcJp2SsG+r/A4ABVzg==",
"dev": true
"integrity": "sha512-P5rExV1phPi42ppoMWy7V63N3i173RY921l4JJ7zonMSxK+OWGPj76GD+cUKUb68l4vQXcJp2SsG+r/A4ABVzg=="
},
"is-descriptor": {
"version": "0.1.6",
@ -10655,8 +10713,7 @@
"is-symbol": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz",
"integrity": "sha512-Z1cLAG7dXM1vJv8mAGjA+XteO0YzYPwD473+qPAWKycNZxwCH/I56QIohKGYCZSB7j6tajrxi/FvOpAfqGhhRQ==",
"dev": true
"integrity": "sha512-Z1cLAG7dXM1vJv8mAGjA+XteO0YzYPwD473+qPAWKycNZxwCH/I56QIohKGYCZSB7j6tajrxi/FvOpAfqGhhRQ=="
},
"is-typedarray": {
"version": "1.0.0",
@ -14533,6 +14590,15 @@
"set-blocking": "~2.0.0"
}
},
"nth-check": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
"integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==",
"dev": true,
"requires": {
"boolbase": "~1.0.0"
}
},
"null-check": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz",
@ -14751,6 +14817,15 @@
}
}
},
"object.getownpropertydescriptors": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
"integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
"requires": {
"define-properties": "^1.1.2",
"es-abstract": "^1.5.1"
}
},
"object.omit": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
@ -15400,6 +15475,15 @@
"integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
"dev": true
},
"parse5": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz",
"integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"parseqs": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
@ -20209,6 +20293,15 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"util.promisify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz",
"integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==",
"requires": {
"define-properties": "^1.1.2",
"object.getownpropertydescriptors": "^2.0.3"
}
},
"utils-merge": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz",
@ -23347,18 +23440,19 @@
}
},
"xml2js": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
"version": "0.4.22",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.22.tgz",
"integrity": "sha512-MWTbxAQqclRSTnehWWe5nMKzI3VmJ8ltiJEco8akcC6j3miOhjjfzKum5sId+CWhfxdOs/1xauYr8/ZDBtQiRw==",
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~9.0.1"
"util.promisify": "~1.0.0",
"xmlbuilder": "~11.0.0"
},
"dependencies": {
"xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ=="
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
}
}
},

View file

@ -115,7 +115,7 @@
"uuid": "^3.0.1",
"v8-profiler-node8": "^6.0.1",
"valid-url": "^1.0.9",
"xml2js": "^0.4.19",
"xml2js": "^0.4.22",
"xregexp": "^4.2.4",
"yauzl": "^2.10.0"
},
@ -128,6 +128,7 @@
"chai": "3.5.0",
"chai-as-promised": "^7.1.1",
"chaid": "^1.0.2",
"cheerio": "^1.0.0-rc.3",
"clean-css-cli": "^4.2.1",
"es6-promise": "^4.0.5",
"eslint": "^4.18.1",

View file

@ -1,4 +1,5 @@
const AuthenticationManager = require('../../../../app/src/Features/Authentication/AuthenticationManager')
const Settings = require('settings-sharelatex')
const UserCreator = require('../../../../app/src/Features/User/UserCreator')
const UserGetter = require('../../../../app/src/Features/User/UserGetter')
const request = require('request-promise-native')
@ -125,6 +126,29 @@ module.exports = class UserHelper {
return new UserHelper(user)
}
static async loginUser(userData) {
if (!userData.email || !userData.password) {
throw new Error('email and password required')
}
const userHelper = new UserHelper()
const loginPath = Settings.enableLegacyLogin ? '/login/legacy' : '/login'
await userHelper.getCsrfToken()
const response = await userHelper.request.post(loginPath, {
json: userData
})
if (response.statusCode !== 200 || response.body.redir !== '/project') {
throw new Error('login failed')
}
userHelper.user = await UserGetter.promises.getUser({
email: userData.email
})
if (!userHelper.user) {
throw new Error(`user not found for email: ${userData.email}`)
}
return userHelper
}
static async registerUser(userData, options = {}) {
const userHelper = new UserHelper()
await userHelper.getCsrfToken()

View file

@ -36,7 +36,8 @@ describe('FeaturesUpdater', function() {
'settings-sharelatex': (this.Settings = {}),
'../Referal/ReferalFeatures': (this.ReferalFeatures = {}),
'./V1SubscriptionManager': (this.V1SubscriptionManager = {}),
'../Institutions/InstitutionsFeatures': (this.InstitutionsFeatures = {})
'../Institutions/InstitutionsFeatures': (this.InstitutionsFeatures = {}),
'../User/UserGetter': (this.UserGetter = {})
}
}))
})
@ -62,6 +63,7 @@ describe('FeaturesUpdater', function() {
this.FeaturesUpdater._mergeFeatures = sinon
.stub()
.returns({ merged: 'features' })
this.UserGetter.getUser = sinon.stub().yields(null, {})
return (this.callback = sinon.stub())
})