first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View File

@@ -0,0 +1,134 @@
const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const { expect } = require('chai')
const modulePath = '../../../../app/src/Features/Helpers/AuthorizationHelper'
describe('AuthorizationHelper', function () {
beforeEach(function () {
this.AuthorizationHelper = SandboxedModule.require(modulePath, {
requires: {
'./AdminAuthorizationHelper': (this.AdminAuthorizationHelper = {
hasAdminAccess: sinon.stub().returns(false),
}),
'../../models/User': {
UserSchema: {
obj: {
staffAccess: {
publisherMetrics: {},
publisherManagement: {},
institutionMetrics: {},
institutionManagement: {},
groupMetrics: {},
groupManagement: {},
adminMetrics: {},
},
},
},
},
'../Project/ProjectGetter': (this.ProjectGetter = { promises: {} }),
'../SplitTests/SplitTestHandler': (this.SplitTestHandler = {
promises: {},
}),
},
})
})
describe('hasAnyStaffAccess', function () {
it('with empty user', function () {
const user = {}
expect(this.AuthorizationHelper.hasAnyStaffAccess(user)).to.be.false
})
it('with no access user', function () {
const user = { isAdmin: false, staffAccess: { adminMetrics: false } }
expect(this.AuthorizationHelper.hasAnyStaffAccess(user)).to.be.false
})
it('with admin user', function () {
const user = { isAdmin: true }
this.AdminAuthorizationHelper.hasAdminAccess.returns(true)
expect(this.AuthorizationHelper.hasAnyStaffAccess(user)).to.be.false
})
it('with staff user', function () {
const user = { staffAccess: { adminMetrics: true, somethingElse: false } }
this.AdminAuthorizationHelper.hasAdminAccess.returns(true)
expect(this.AuthorizationHelper.hasAnyStaffAccess(user)).to.be.true
})
it('with non-staff user with extra attributes', function () {
// make sure that staffAccess attributes not declared on the model don't
// give user access
const user = { staffAccess: { adminMetrics: false, somethingElse: true } }
expect(this.AuthorizationHelper.hasAnyStaffAccess(user)).to.be.false
})
})
describe('isReviewerRoleEnabled', function () {
it('with no reviewers and no split test', async function () {
this.ProjectGetter.promises.getProject = sinon.stub().resolves({
reviewer_refs: {},
owner_ref: 'ownerId',
})
this.SplitTestHandler.promises.getAssignmentForUser = sinon
.stub()
.resolves({
variant: 'disabled',
})
expect(
await this.AuthorizationHelper.promises.isReviewerRoleEnabled(
'projectId'
)
).to.be.false
})
it('with no reviewers and enabled split test', async function () {
this.ProjectGetter.promises.getProject = sinon.stub().resolves({
reviewer_refs: {},
owner_ref: 'userId',
})
this.SplitTestHandler.promises.getAssignmentForUser = sinon
.stub()
.resolves({
variant: 'enabled',
})
expect(
await this.AuthorizationHelper.promises.isReviewerRoleEnabled(
'projectId'
)
).to.be.true
})
it('with reviewers and disabled split test', async function () {
this.ProjectGetter.promises.getProject = sinon.stub().resolves({
reviewer_refs: [{ $oid: 'userId' }],
})
this.SplitTestHandler.promises.getAssignmentForUser = sinon
.stub()
.resolves({
variant: 'default',
})
expect(
await this.AuthorizationHelper.promises.isReviewerRoleEnabled(
'projectId'
)
).to.be.true
})
it('with reviewers and enabled split test', async function () {
this.ProjectGetter.promises.getProject = sinon.stub().resolves({
reviewer_refs: [{ $oid: 'userId' }],
})
this.SplitTestHandler.promises.getAssignmentForUser = sinon
.stub()
.resolves({
variant: 'enabled',
})
expect(
await this.AuthorizationHelper.promises.isReviewerRoleEnabled(
'projectId'
)
).to.be.true
})
})
})

View File

@@ -0,0 +1,28 @@
const { expect } = require('chai')
const {
stringSimilarity,
} = require('../../../../app/src/Features/Helpers/DiffHelper')
describe('DiffHelper', function () {
describe('stringSimilarity', function () {
it('should have a ratio of 1 for identical strings', function () {
expect(stringSimilarity('abcdef', 'abcdef')).to.equal(1.0)
})
it('should have a ratio of 0 for completely different strings', function () {
expect(stringSimilarity('abcdef', 'qmglzxv')).to.equal(0.0)
})
it('should have a ratio of between 0 and 1 for strings that are similar', function () {
const ratio = stringSimilarity('abcdef', 'abcdef@zxvkp')
expect(ratio).to.equal(0.66)
})
it('should reject non-string inputs', function () {
expect(() => stringSimilarity(1, 'abc')).to.throw
expect(() => stringSimilarity('abc', 2)).to.throw
expect(() => stringSimilarity('abc', new Array(1000).fill('a').join('')))
.to.throw
})
})
})

View File

@@ -0,0 +1,56 @@
const { expect } = require('chai')
const {
parseEmail,
} = require('../../../../app/src/Features/Helpers/EmailHelper')
describe('EmailHelper', function () {
it('should parse a single email', function () {
const address = 'test@example.com'
const expected = 'test@example.com'
expect(parseEmail(address)).to.equal(expected)
expect(parseEmail(address, true)).to.equal(expected)
})
it('should parse a valid email address', function () {
const address = '"Test Person" <test@example.com>'
const expected = 'test@example.com'
expect(parseEmail(address)).to.equal(null)
expect(parseEmail(address, true)).to.equal(expected)
})
it('should return null for garbage input', function () {
const cases = [
undefined,
null,
'',
42,
['test@example.com'],
{},
{ length: 42 },
{ trim: true, match: true },
{ toString: true },
]
for (const input of cases) {
expect(parseEmail(input)).to.equal(null, input)
expect(parseEmail(input, true)).to.equal(null, input)
}
})
it('should return null for an invalid single email', function () {
const address = 'testexample.com'
expect(parseEmail(address)).to.equal(null)
expect(parseEmail(address, true)).to.equal(null)
})
it('should return null for an invalid email address', function () {
const address = '"Test Person" test@example.com>'
expect(parseEmail(address)).to.equal(null)
expect(parseEmail(address, true)).to.equal(null)
})
it('should return null for a group of addresses', function () {
const address = 'Group name:test1@example.com,test2@example.com;'
expect(parseEmail(address)).to.equal(null)
expect(parseEmail(address, true)).to.equal(null)
})
})

View File

@@ -0,0 +1,156 @@
const { expect } = require('chai')
const SandboxedModule = require('sandboxed-module')
const MODULE_PATH = require('path').join(
__dirname,
'../../../../app/src/Features/Helpers/SafeHTMLSubstitution.js'
)
describe('SafeHTMLSubstitution', function () {
let SafeHTMLSubstitution
before(function () {
SafeHTMLSubstitution = SandboxedModule.require(MODULE_PATH)
})
describe('SPLIT_REGEX', function () {
const CASES = {
'PRE<0>INNER</0>POST': ['PRE', '0', 'INNER', 'POST'],
'<0>INNER</0>': ['', '0', 'INNER', ''],
'<0></0>': ['', '0', '', ''],
'<0>INNER</0><0>INNER2</0>': ['', '0', 'INNER', '', '0', 'INNER2', ''],
'<0><1>INNER</1></0>': ['', '0', '<1>INNER</1>', ''],
'PLAIN TEXT': ['PLAIN TEXT'],
}
Object.entries(CASES).forEach(([input, output]) => {
it(`should parse "${input}" as expected`, function () {
expect(input.split(SafeHTMLSubstitution.SPLIT_REGEX)).to.deep.equal(
output
)
})
})
})
describe('render', function () {
describe('substitution', function () {
it('should substitute a single component', function () {
expect(
SafeHTMLSubstitution.render('<0>good</0>', [{ name: 'b' }])
).to.equal('<b>good</b>')
})
it('should substitute a single component as string', function () {
expect(SafeHTMLSubstitution.render('<0>good</0>', ['b'])).to.equal(
'<b>good</b>'
)
})
it('should substitute a single component twice', function () {
expect(
SafeHTMLSubstitution.render('<0>one</0><0>two</0>', [{ name: 'b' }])
).to.equal('<b>one</b><b>two</b>')
})
it('should substitute two components', function () {
expect(
SafeHTMLSubstitution.render('<0>one</0><1>two</1>', [
{ name: 'b' },
{ name: 'i' },
])
).to.equal('<b>one</b><i>two</i>')
})
it('should substitute a single component with a class', function () {
expect(
SafeHTMLSubstitution.render('<0>text</0>', [
{
name: 'b',
attrs: {
class: 'magic',
},
},
])
).to.equal('<b class="magic">text</b>')
})
it('should substitute two nested components', function () {
expect(
SafeHTMLSubstitution.render('<0><1>nested</1></0>', [
{ name: 'b' },
{ name: 'i' },
])
).to.equal('<b><i>nested</i></b>')
})
it('should handle links', function () {
expect(
SafeHTMLSubstitution.render('<0>Go to Login</0>', [
{ name: 'a', attrs: { href: 'https://www.overleaf.com/login' } },
])
).to.equal('<a href="https://www.overleaf.com/login">Go to Login</a>')
})
it('should not complain about too many components', function () {
expect(
SafeHTMLSubstitution.render('<0>good</0>', [
{ name: 'b' },
{ name: 'i' },
{ name: 'u' },
])
).to.equal('<b>good</b>')
})
})
describe('pug.escape', function () {
it('should handle plain text', function () {
expect(SafeHTMLSubstitution.render('plain text')).to.equal('plain text')
})
it('should keep a simple string delimiter', function () {
expect(SafeHTMLSubstitution.render("'")).to.equal(`'`)
})
it('should escape double quotes', function () {
expect(SafeHTMLSubstitution.render('"')).to.equal(`&quot;`)
})
it('should escape &', function () {
expect(SafeHTMLSubstitution.render('&')).to.equal(`&amp;`)
})
it('should escape <', function () {
expect(SafeHTMLSubstitution.render('<')).to.equal(`&lt;`)
})
it('should escape >', function () {
expect(SafeHTMLSubstitution.render('>')).to.equal(`&gt;`)
})
it('should escape html', function () {
expect(SafeHTMLSubstitution.render('<b>bad</b>')).to.equal(
'&lt;b&gt;bad&lt;/b&gt;'
)
})
})
describe('escape around substitutions', function () {
it('should escape text inside a component', function () {
expect(
SafeHTMLSubstitution.render('<0><i>inner</i></0>', [{ name: 'b' }])
).to.equal('<b>&lt;i&gt;inner&lt;/i&gt;</b>')
})
it('should escape text in front of a component', function () {
expect(
SafeHTMLSubstitution.render('<i>PRE</i><0>inner</0>', [{ name: 'b' }])
).to.equal('&lt;i&gt;PRE&lt;/i&gt;<b>inner</b>')
})
it('should escape text after of a component', function () {
expect(
SafeHTMLSubstitution.render('<0>inner</0><i>POST</i>', [
{ name: 'b' },
])
).to.equal('<b>inner</b>&lt;i&gt;POST&lt;/i&gt;')
})
})
})
})

View File

@@ -0,0 +1,44 @@
const { expect } = require('chai')
const SandboxedModule = require('sandboxed-module')
const modulePath = require('path').join(
__dirname,
'../../../../app/src/Features/Helpers/UrlHelper.js'
)
describe('UrlHelper', function () {
beforeEach(function () {
this.settings = {
apis: { linkedUrlProxy: { url: undefined } },
siteUrl: 'http://127.0.0.1:3000',
}
this.UrlHelper = SandboxedModule.require(modulePath, {
requires: { '@overleaf/settings': this.settings },
})
})
describe('getSafeRedirectPath', function () {
it('sanitize redirect path to prevent open redirects', function () {
expect(this.UrlHelper.getSafeRedirectPath('https://evil.com')).to.be
.undefined
expect(this.UrlHelper.getSafeRedirectPath('//evil.com')).to.be.undefined
expect(this.UrlHelper.getSafeRedirectPath('//ol.com/evil')).to.equal(
'/evil'
)
expect(this.UrlHelper.getSafeRedirectPath('////evil.com')).to.be.undefined
expect(this.UrlHelper.getSafeRedirectPath('%2F%2Fevil.com')).to.equal(
'/%2F%2Fevil.com'
)
expect(
this.UrlHelper.getSafeRedirectPath('http://foo.com//evil.com/bad')
).to.equal('/evil.com/bad')
return expect(this.UrlHelper.getSafeRedirectPath('.evil.com')).to.equal(
'/.evil.com'
)
})
})
})