mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #15326 from overleaf/jel-link-sharing
[web] Add prefix of token hash to link sharing URLs GitOrigin-RevId: 4b764c076a335768ab261dd1e181d90ce00fd1a2
This commit is contained in:
parent
c03f2807bf
commit
8da063d640
11 changed files with 262 additions and 108 deletions
|
@ -167,5 +167,17 @@ async function getShareTokens(req, res) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tokens.readOnly) {
|
||||||
|
tokens.readOnlyHashPrefix = TokenAccessHandler.createTokenHashPrefix(
|
||||||
|
tokens.readOnly
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokens.readAndWrite) {
|
||||||
|
tokens.readAndWriteHashPrefix = TokenAccessHandler.createTokenHashPrefix(
|
||||||
|
tokens.readAndWrite
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
res.json(tokens)
|
res.json(tokens)
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,7 @@ async function tokenAccessPage(req, res, next) {
|
||||||
return res.redirect(302, docPublishedInfo.published_path)
|
return res.redirect(302, docPublishedInfo.published_path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.render('project/token/access', {
|
res.render('project/token/access', {
|
||||||
postUrl: makePostUrl(token),
|
postUrl: makePostUrl(token),
|
||||||
})
|
})
|
||||||
|
@ -225,12 +226,15 @@ async function checkAndGetProjectOrResponseAction(
|
||||||
|
|
||||||
async function grantTokenAccessReadAndWrite(req, res, next) {
|
async function grantTokenAccessReadAndWrite(req, res, next) {
|
||||||
const { token } = req.params
|
const { token } = req.params
|
||||||
const { confirmedByUser } = req.body
|
const { confirmedByUser, tokenHashPrefix } = req.body
|
||||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||||
if (!TokenAccessHandler.isReadAndWriteToken(token)) {
|
if (!TokenAccessHandler.isReadAndWriteToken(token)) {
|
||||||
return res.sendStatus(400)
|
return res.sendStatus(400)
|
||||||
}
|
}
|
||||||
const tokenType = TokenAccessHandler.TOKEN_TYPES.READ_AND_WRITE
|
const tokenType = TokenAccessHandler.TOKEN_TYPES.READ_AND_WRITE
|
||||||
|
|
||||||
|
TokenAccessHandler.checkTokenHashPrefix(token, tokenHashPrefix, tokenType)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [project, action] = await checkAndGetProjectOrResponseAction(
|
const [project, action] = await checkAndGetProjectOrResponseAction(
|
||||||
tokenType,
|
tokenType,
|
||||||
|
@ -268,6 +272,7 @@ async function grantTokenAccessReadAndWrite(req, res, next) {
|
||||||
userId,
|
userId,
|
||||||
project._id
|
project._id
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
redirect: `/project/${project._id}`,
|
redirect: `/project/${project._id}`,
|
||||||
tokenAccessGranted: tokenType,
|
tokenAccessGranted: tokenType,
|
||||||
|
@ -285,12 +290,16 @@ async function grantTokenAccessReadAndWrite(req, res, next) {
|
||||||
|
|
||||||
async function grantTokenAccessReadOnly(req, res, next) {
|
async function grantTokenAccessReadOnly(req, res, next) {
|
||||||
const { token } = req.params
|
const { token } = req.params
|
||||||
const { confirmedByUser } = req.body
|
const { confirmedByUser, tokenHashPrefix } = req.body
|
||||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||||
if (!TokenAccessHandler.isReadOnlyToken(token)) {
|
if (!TokenAccessHandler.isReadOnlyToken(token)) {
|
||||||
return res.sendStatus(400)
|
return res.sendStatus(400)
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenType = TokenAccessHandler.TOKEN_TYPES.READ_ONLY
|
const tokenType = TokenAccessHandler.TOKEN_TYPES.READ_ONLY
|
||||||
|
|
||||||
|
TokenAccessHandler.checkTokenHashPrefix(token, tokenHashPrefix, tokenType)
|
||||||
|
|
||||||
const docPublishedInfo =
|
const docPublishedInfo =
|
||||||
await TokenAccessHandler.promises.getV1DocPublishedInfo(token)
|
await TokenAccessHandler.promises.getV1DocPublishedInfo(token)
|
||||||
if (docPublishedInfo.allow === false) {
|
if (docPublishedInfo.allow === false) {
|
||||||
|
@ -333,6 +342,7 @@ async function grantTokenAccessReadOnly(req, res, next) {
|
||||||
userId,
|
userId,
|
||||||
project._id
|
project._id
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
redirect: `/project/${project._id}`,
|
redirect: `/project/${project._id}`,
|
||||||
tokenAccessGranted: tokenType,
|
tokenAccessGranted: tokenType,
|
||||||
|
|
|
@ -2,6 +2,7 @@ const { Project } = require('../../models/Project')
|
||||||
const PublicAccessLevels = require('../Authorization/PublicAccessLevels')
|
const PublicAccessLevels = require('../Authorization/PublicAccessLevels')
|
||||||
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
||||||
const { ObjectId } = require('mongodb')
|
const { ObjectId } = require('mongodb')
|
||||||
|
const Metrics = require('@overleaf/metrics')
|
||||||
const Settings = require('@overleaf/settings')
|
const Settings = require('@overleaf/settings')
|
||||||
const logger = require('@overleaf/logger')
|
const logger = require('@overleaf/logger')
|
||||||
const V1Api = require('../V1/V1Api')
|
const V1Api = require('../V1/V1Api')
|
||||||
|
@ -279,6 +280,32 @@ const TokenAccessHandler = {
|
||||||
callback(null, body)
|
callback(null, body)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createTokenHashPrefix(token) {
|
||||||
|
const hash = crypto.createHash('sha256')
|
||||||
|
hash.update(token)
|
||||||
|
return hash.digest('hex').slice(0, 6)
|
||||||
|
},
|
||||||
|
|
||||||
|
checkTokenHashPrefix(token, tokenHashPrefix, type) {
|
||||||
|
let hashPrefixStatus
|
||||||
|
|
||||||
|
if (!tokenHashPrefix) {
|
||||||
|
hashPrefixStatus = 'missing'
|
||||||
|
} else {
|
||||||
|
const hashPrefix = TokenAccessHandler.createTokenHashPrefix(token)
|
||||||
|
if (hashPrefix === tokenHashPrefix.replace('#', '')) {
|
||||||
|
hashPrefixStatus = 'match'
|
||||||
|
} else {
|
||||||
|
hashPrefixStatus = 'mismatch'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Metrics.inc('link-sharing.hash-check', {
|
||||||
|
path: type,
|
||||||
|
status: hashPrefixStatus,
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenAccessHandler.promises = promisifyAll(TokenAccessHandler, {
|
TokenAccessHandler.promises = promisifyAll(TokenAccessHandler, {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { getJSON } from '../../../infrastructure/fetch-json'
|
||||||
import useAbortController from '../../../shared/hooks/use-abort-controller'
|
import useAbortController from '../../../shared/hooks/use-abort-controller'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
|
||||||
export default function LinkSharing({ canAddCollaborators }) {
|
export default function LinkSharing() {
|
||||||
const [inflight, setInflight] = useState(false)
|
const [inflight, setInflight] = useState(false)
|
||||||
|
|
||||||
const { monitorRequest } = useShareProjectContext()
|
const { monitorRequest } = useShareProjectContext()
|
||||||
|
@ -61,7 +61,6 @@ export default function LinkSharing({ canAddCollaborators }) {
|
||||||
<TokenBasedSharing
|
<TokenBasedSharing
|
||||||
setAccessLevel={setAccessLevel}
|
setAccessLevel={setAccessLevel}
|
||||||
inflight={inflight}
|
inflight={inflight}
|
||||||
canAddCollaborators={canAddCollaborators}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -81,10 +80,6 @@ export default function LinkSharing({ canAddCollaborators }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LinkSharing.propTypes = {
|
|
||||||
canAddCollaborators: PropTypes.bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
function PrivateSharing({ setAccessLevel, inflight, projectId }) {
|
function PrivateSharing({ setAccessLevel, inflight, projectId }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
|
@ -117,7 +112,7 @@ PrivateSharing.propTypes = {
|
||||||
projectId: PropTypes.string,
|
projectId: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
function TokenBasedSharing({ setAccessLevel, inflight, canAddCollaborators }) {
|
function TokenBasedSharing({ setAccessLevel, inflight }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { _id: projectId } = useProjectContext()
|
const { _id: projectId } = useProjectContext()
|
||||||
|
|
||||||
|
@ -152,6 +147,7 @@ function TokenBasedSharing({ setAccessLevel, inflight, canAddCollaborators }) {
|
||||||
<strong>{t('anyone_with_link_can_edit')}</strong>
|
<strong>{t('anyone_with_link_can_edit')}</strong>
|
||||||
<AccessToken
|
<AccessToken
|
||||||
token={tokens?.readAndWrite}
|
token={tokens?.readAndWrite}
|
||||||
|
tokenHashPrefix={tokens?.readAndWriteHashPrefix}
|
||||||
path="/"
|
path="/"
|
||||||
tooltipId="tooltip-copy-link-rw"
|
tooltipId="tooltip-copy-link-rw"
|
||||||
/>
|
/>
|
||||||
|
@ -160,6 +156,7 @@ function TokenBasedSharing({ setAccessLevel, inflight, canAddCollaborators }) {
|
||||||
<strong>{t('anyone_with_link_can_view')}</strong>
|
<strong>{t('anyone_with_link_can_view')}</strong>
|
||||||
<AccessToken
|
<AccessToken
|
||||||
token={tokens?.readOnly}
|
token={tokens?.readOnly}
|
||||||
|
tokenHashPrefix={tokens?.readOnlyHashPrefix}
|
||||||
path="/read/"
|
path="/read/"
|
||||||
tooltipId="tooltip-copy-link-ro"
|
tooltipId="tooltip-copy-link-ro"
|
||||||
/>
|
/>
|
||||||
|
@ -172,7 +169,6 @@ function TokenBasedSharing({ setAccessLevel, inflight, canAddCollaborators }) {
|
||||||
TokenBasedSharing.propTypes = {
|
TokenBasedSharing.propTypes = {
|
||||||
setAccessLevel: PropTypes.func.isRequired,
|
setAccessLevel: PropTypes.func.isRequired,
|
||||||
inflight: PropTypes.bool,
|
inflight: PropTypes.bool,
|
||||||
canAddCollaborators: PropTypes.bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function LegacySharing({ accessLevel, setAccessLevel, inflight }) {
|
function LegacySharing({ accessLevel, setAccessLevel, inflight }) {
|
||||||
|
@ -229,6 +225,7 @@ export function ReadOnlyTokenLink() {
|
||||||
<strong>{t('anyone_with_link_can_view')}</strong>
|
<strong>{t('anyone_with_link_can_view')}</strong>
|
||||||
<AccessToken
|
<AccessToken
|
||||||
token={tokens?.readOnly}
|
token={tokens?.readOnly}
|
||||||
|
tokenHashPrefix={tokens?.readOnlyHashPrefix}
|
||||||
path="/read/"
|
path="/read/"
|
||||||
tooltipId="tooltip-copy-link-ro"
|
tooltipId="tooltip-copy-link-ro"
|
||||||
/>
|
/>
|
||||||
|
@ -238,7 +235,7 @@ export function ReadOnlyTokenLink() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function AccessToken({ token, path, tooltipId }) {
|
function AccessToken({ token, tokenHashPrefix, path, tooltipId }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { isAdmin } = useUserContext()
|
const { isAdmin } = useUserContext()
|
||||||
|
|
||||||
|
@ -254,7 +251,9 @@ function AccessToken({ token, path, tooltipId }) {
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
origin = window.ExposedSettings.siteUrl
|
origin = window.ExposedSettings.siteUrl
|
||||||
}
|
}
|
||||||
const link = `${origin}${path}${token}`
|
const link = `${origin}${path}${token}${
|
||||||
|
tokenHashPrefix ? `#${tokenHashPrefix}` : ''
|
||||||
|
}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="access-token">
|
<div className="access-token">
|
||||||
|
@ -266,6 +265,7 @@ function AccessToken({ token, path, tooltipId }) {
|
||||||
|
|
||||||
AccessToken.propTypes = {
|
AccessToken.propTypes = {
|
||||||
token: PropTypes.string,
|
token: PropTypes.string,
|
||||||
|
tokenHashPrefix: PropTypes.string,
|
||||||
tooltipId: PropTypes.string.isRequired,
|
tooltipId: PropTypes.string.isRequired,
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ export default function ShareModalBody() {
|
||||||
{isProjectOwner && (
|
{isProjectOwner && (
|
||||||
<>
|
<>
|
||||||
<br />
|
<br />
|
||||||
<LinkSharing canAddCollaborators={canAddCollaborators} />
|
<LinkSharing />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ export default function ShareModalBody() {
|
||||||
{isProjectOwner && (
|
{isProjectOwner && (
|
||||||
<>
|
<>
|
||||||
<br />
|
<br />
|
||||||
<LinkSharing canAddCollaborators={canAddCollaborators} />
|
<LinkSharing />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -122,9 +122,7 @@ export default function ShareModalBody() {
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isProjectOwner && (
|
{isProjectOwner && <LinkSharing />}
|
||||||
<LinkSharing canAddCollaborators={canAddCollaborators} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<OwnerInfo />
|
<OwnerInfo />
|
||||||
|
|
||||||
|
|
|
@ -43,13 +43,13 @@ App.controller('TokenAccessPageController', [
|
||||||
const parsedData = JSON.parse(textData)
|
const parsedData = JSON.parse(textData)
|
||||||
const { postUrl, csrfToken } = parsedData
|
const { postUrl, csrfToken } = parsedData
|
||||||
$scope.accessInFlight = true
|
$scope.accessInFlight = true
|
||||||
|
|
||||||
$http({
|
$http({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: postUrl,
|
url: postUrl,
|
||||||
data: {
|
data: {
|
||||||
_csrf: csrfToken,
|
_csrf: csrfToken,
|
||||||
confirmedByUser,
|
confirmedByUser,
|
||||||
|
tokenHashPrefix: window.location.hash,
|
||||||
},
|
},
|
||||||
}).then(
|
}).then(
|
||||||
function successCallback(response) {
|
function successCallback(response) {
|
||||||
|
|
|
@ -345,11 +345,14 @@ describe('TokenAccess', function () {
|
||||||
(error, response, body) => {
|
(error, response, body) => {
|
||||||
expect(error).to.equal(null)
|
expect(error).to.equal(null)
|
||||||
expect(response.statusCode).to.equal(200)
|
expect(response.statusCode).to.equal(200)
|
||||||
expect(body).to.deep.equal({
|
expect(body).to.include({
|
||||||
readOnly: this.tokens.readOnly,
|
readOnly: this.tokens.readOnly,
|
||||||
readAndWrite: this.tokens.readAndWrite,
|
readAndWrite: this.tokens.readAndWrite,
|
||||||
readAndWritePrefix: this.tokens.readAndWritePrefix,
|
readAndWritePrefix: this.tokens.readAndWritePrefix,
|
||||||
})
|
})
|
||||||
|
expect(body.readOnlyHashPrefix).to.exist
|
||||||
|
expect(body.readAndWriteHashPrefix).to.exist
|
||||||
|
expect(Object.keys(body).length).to.equal(5)
|
||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -494,7 +497,8 @@ describe('TokenAccess', function () {
|
||||||
(error, response, body) => {
|
(error, response, body) => {
|
||||||
expect(error).to.equal(null)
|
expect(error).to.equal(null)
|
||||||
expect(response.statusCode).to.equal(200)
|
expect(response.statusCode).to.equal(200)
|
||||||
expect(body).to.deep.equal({ readOnly: this.tokens.readOnly })
|
expect(body).to.include({ readOnly: this.tokens.readOnly })
|
||||||
|
expect(body.readOnlyHashPrefix).to.exist
|
||||||
cb()
|
cb()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -709,7 +713,9 @@ describe('TokenAccess', function () {
|
||||||
(error, response, body) => {
|
(error, response, body) => {
|
||||||
expect(error).to.equal(null)
|
expect(error).to.equal(null)
|
||||||
expect(response.statusCode).to.equal(200)
|
expect(response.statusCode).to.equal(200)
|
||||||
expect(body).to.deep.equal({ readOnly: this.tokens.readOnly })
|
expect(body).to.include({ readOnly: this.tokens.readOnly })
|
||||||
|
expect(body.readOnlyHashPrefix).to.exist
|
||||||
|
expect(Object.keys(body).length).to.equal(2)
|
||||||
cb()
|
cb()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -146,6 +146,14 @@ describe('<ShareProjectModal/>', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('handles access level "tokenBased"', async function () {
|
it('handles access level "tokenBased"', async function () {
|
||||||
|
const tokens = {
|
||||||
|
readAndWrite: '6862414195fwtbrtrdtskb',
|
||||||
|
readAndWritePrefix: '6862414195',
|
||||||
|
readOnly: 'wrnjfzkysqkr',
|
||||||
|
readAndWriteHashPrefix: 'taEVki',
|
||||||
|
readOnlyHashPrefix: 'j2xYbL',
|
||||||
|
}
|
||||||
|
fetchMock.get(`/project/${project._id}/tokens`, tokens)
|
||||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||||
scope: { project: { ...project, publicAccesLevel: 'tokenBased' } },
|
scope: { project: { ...project, publicAccesLevel: 'tokenBased' } },
|
||||||
})
|
})
|
||||||
|
@ -157,6 +165,13 @@ describe('<ShareProjectModal/>', function () {
|
||||||
.not.to.be.null
|
.not.to.be.null
|
||||||
expect(screen.queryByText('Anyone with this link can edit this project'))
|
expect(screen.queryByText('Anyone with this link can edit this project'))
|
||||||
.not.to.be.null
|
.not.to.be.null
|
||||||
|
|
||||||
|
screen.getByText(
|
||||||
|
`https://www.test-overleaf.com/${tokens.readAndWrite}#${tokens.readAndWriteHashPrefix}`
|
||||||
|
)
|
||||||
|
screen.getByText(
|
||||||
|
`https://www.test-overleaf.com/read/${tokens.readOnly}#${tokens.readOnlyHashPrefix}`
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('handles legacy access level "readAndWrite"', async function () {
|
it('handles legacy access level "readAndWrite"', async function () {
|
||||||
|
|
|
@ -23,6 +23,7 @@ describe('CollaboratorsController', function () {
|
||||||
removeUserFromProject: sinon.stub().resolves(),
|
removeUserFromProject: sinon.stub().resolves(),
|
||||||
setCollaboratorPrivilegeLevel: sinon.stub().resolves(),
|
setCollaboratorPrivilegeLevel: sinon.stub().resolves(),
|
||||||
},
|
},
|
||||||
|
createTokenHashPrefix: sinon.stub().returns('abc123'),
|
||||||
}
|
}
|
||||||
this.CollaboratorsGetter = {
|
this.CollaboratorsGetter = {
|
||||||
promises: {
|
promises: {
|
||||||
|
|
|
@ -29,6 +29,7 @@ describe('TokenAccessController', function () {
|
||||||
isReadAndWriteToken: sinon.stub().returns(true),
|
isReadAndWriteToken: sinon.stub().returns(true),
|
||||||
isReadOnlyToken: sinon.stub().returns(true),
|
isReadOnlyToken: sinon.stub().returns(true),
|
||||||
tokenAccessEnabledForProject: sinon.stub().returns(true),
|
tokenAccessEnabledForProject: sinon.stub().returns(true),
|
||||||
|
checkTokenHashPrefix: sinon.stub(),
|
||||||
promises: {
|
promises: {
|
||||||
addReadAndWriteUserToProject: sinon.stub().resolves(),
|
addReadAndWriteUserToProject: sinon.stub().resolves(),
|
||||||
addReadOnlyUserToProject: sinon.stub().resolves(),
|
addReadOnlyUserToProject: sinon.stub().resolves(),
|
||||||
|
@ -78,7 +79,7 @@ describe('TokenAccessController', function () {
|
||||||
describe('normal case', function () {
|
describe('normal case', function () {
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
this.req.params = { token: this.token }
|
this.req.params = { token: this.token }
|
||||||
this.req.body = { confirmedByUser: true }
|
this.req.body = { confirmedByUser: true, tokenHashPrefix: 'prefix' }
|
||||||
this.res.callback = done
|
this.res.callback = done
|
||||||
this.TokenAccessController.grantTokenAccessReadAndWrite(
|
this.TokenAccessController.grantTokenAccessReadAndWrite(
|
||||||
this.req,
|
this.req,
|
||||||
|
@ -104,6 +105,12 @@ describe('TokenAccessController', function () {
|
||||||
{ privileges: 'readAndWrite' }
|
{ privileges: 'readAndWrite' }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('checks token hash', function () {
|
||||||
|
expect(
|
||||||
|
this.TokenAccessHandler.checkTokenHashPrefix
|
||||||
|
).to.have.been.calledWith(this.token, 'prefix', 'readAndWrite')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when the access was already granted', function () {
|
describe('when the access was already granted', function () {
|
||||||
|
@ -124,13 +131,38 @@ describe('TokenAccessController', function () {
|
||||||
.called
|
.called
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('hash prefix missing in request', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
this.req.params = { token: this.token }
|
||||||
|
this.req.body = { confirmedByUser: true }
|
||||||
|
this.res.callback = done
|
||||||
|
this.TokenAccessController.grantTokenAccessReadAndWrite(
|
||||||
|
this.req,
|
||||||
|
this.res,
|
||||||
|
done
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('grants read and write access', function () {
|
||||||
|
expect(
|
||||||
|
this.TokenAccessHandler.promises.addReadAndWriteUserToProject
|
||||||
|
).to.have.been.calledWith(this.user._id, this.project._id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends missing hash to metrics', function () {
|
||||||
|
expect(
|
||||||
|
this.TokenAccessHandler.checkTokenHashPrefix
|
||||||
|
).to.have.been.calledWith(this.token, undefined, 'readAndWrite')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('grantTokenAccessReadOnly', function () {
|
describe('grantTokenAccessReadOnly', function () {
|
||||||
describe('normal case', function () {
|
describe('normal case', function () {
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
this.req.params = { token: this.token }
|
this.req.params = { token: this.token }
|
||||||
this.req.body = { confirmedByUser: true }
|
this.req.body = { confirmedByUser: true, tokenHashPrefix: 'prefix' }
|
||||||
this.res.callback = done
|
this.res.callback = done
|
||||||
this.TokenAccessController.grantTokenAccessReadOnly(
|
this.TokenAccessController.grantTokenAccessReadOnly(
|
||||||
this.req,
|
this.req,
|
||||||
|
@ -156,6 +188,12 @@ describe('TokenAccessController', function () {
|
||||||
{ privileges: 'readOnly' }
|
{ privileges: 'readOnly' }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('sends checks if hash prefix matches', function () {
|
||||||
|
expect(
|
||||||
|
this.TokenAccessHandler.checkTokenHashPrefix
|
||||||
|
).to.have.been.calledWith(this.token, 'prefix', 'readOnly')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when the access was already granted', function () {
|
describe('when the access was already granted', function () {
|
||||||
|
|
|
@ -1,16 +1,3 @@
|
||||||
/* eslint-disable
|
|
||||||
n/handle-callback-err,
|
|
||||||
max-len,
|
|
||||||
no-return-assign,
|
|
||||||
no-unused-vars,
|
|
||||||
*/
|
|
||||||
// TODO: This file was created by bulk-decaffeinate.
|
|
||||||
// Fix any style issues and re-enable lint.
|
|
||||||
/*
|
|
||||||
* decaffeinate suggestions:
|
|
||||||
* DS102: Remove unnecessary code created because of implicit returns
|
|
||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
||||||
*/
|
|
||||||
const SandboxedModule = require('sandboxed-module')
|
const SandboxedModule = require('sandboxed-module')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const sinon = require('sinon')
|
const sinon = require('sinon')
|
||||||
|
@ -32,10 +19,11 @@ describe('TokenAccessHandler', function () {
|
||||||
}
|
}
|
||||||
this.userId = ObjectId()
|
this.userId = ObjectId()
|
||||||
this.req = {}
|
this.req = {}
|
||||||
return (this.TokenAccessHandler = SandboxedModule.require(modulePath, {
|
this.TokenAccessHandler = SandboxedModule.require(modulePath, {
|
||||||
requires: {
|
requires: {
|
||||||
mongodb: { ObjectId },
|
mongodb: { ObjectId },
|
||||||
'../../models/Project': { Project: (this.Project = {}) },
|
'../../models/Project': { Project: (this.Project = {}) },
|
||||||
|
'@overleaf/metrics': (this.Metrics = { inc: sinon.stub() }),
|
||||||
'@overleaf/settings': (this.settings = {}),
|
'@overleaf/settings': (this.settings = {}),
|
||||||
'../V1/V1Api': (this.V1Api = {
|
'../V1/V1Api': (this.V1Api = {
|
||||||
request: sinon.stub(),
|
request: sinon.stub(),
|
||||||
|
@ -45,7 +33,7 @@ describe('TokenAccessHandler', function () {
|
||||||
recordEventForUser: sinon.stub(),
|
recordEventForUser: sinon.stub(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}))
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getTokenType', function () {
|
describe('getTokenType', function () {
|
||||||
|
@ -119,14 +107,15 @@ describe('TokenAccessHandler', function () {
|
||||||
|
|
||||||
describe('addReadOnlyUserToProject', function () {
|
describe('addReadOnlyUserToProject', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
return (this.Project.updateOne = sinon.stub().callsArgWith(2, null))
|
this.Project.updateOne = sinon.stub().callsArgWith(2, null)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call Project.updateOne', function (done) {
|
it('should call Project.updateOne', function (done) {
|
||||||
return this.TokenAccessHandler.addReadOnlyUserToProject(
|
this.TokenAccessHandler.addReadOnlyUserToProject(
|
||||||
this.userId,
|
this.userId,
|
||||||
this.projectId,
|
this.projectId,
|
||||||
err => {
|
err => {
|
||||||
|
expect(err).to.not.exist
|
||||||
expect(this.Project.updateOne.callCount).to.equal(1)
|
expect(this.Project.updateOne.callCount).to.equal(1)
|
||||||
expect(
|
expect(
|
||||||
this.Project.updateOne.calledWith({
|
this.Project.updateOne.calledWith({
|
||||||
|
@ -142,36 +131,36 @@ describe('TokenAccessHandler', function () {
|
||||||
'project-joined',
|
'project-joined',
|
||||||
{ mode: 'read-only' }
|
{ mode: 'read-only' }
|
||||||
)
|
)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not produce an error', function (done) {
|
it('should not produce an error', function (done) {
|
||||||
return this.TokenAccessHandler.addReadOnlyUserToProject(
|
this.TokenAccessHandler.addReadOnlyUserToProject(
|
||||||
this.userId,
|
this.userId,
|
||||||
this.projectId,
|
this.projectId,
|
||||||
err => {
|
err => {
|
||||||
expect(err).to.not.exist
|
expect(err).to.not.exist
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when Project.updateOne produces an error', function () {
|
describe('when Project.updateOne produces an error', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
return (this.Project.updateOne = sinon
|
this.Project.updateOne = sinon
|
||||||
.stub()
|
.stub()
|
||||||
.callsArgWith(2, new Error('woops')))
|
.callsArgWith(2, new Error('woops'))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should produce an error', function (done) {
|
it('should produce an error', function (done) {
|
||||||
return this.TokenAccessHandler.addReadOnlyUserToProject(
|
this.TokenAccessHandler.addReadOnlyUserToProject(
|
||||||
this.userId,
|
this.userId,
|
||||||
this.projectId,
|
this.projectId,
|
||||||
err => {
|
err => {
|
||||||
expect(err).to.exist
|
expect(err).to.exist
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -180,14 +169,15 @@ describe('TokenAccessHandler', function () {
|
||||||
|
|
||||||
describe('addReadAndWriteUserToProject', function () {
|
describe('addReadAndWriteUserToProject', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
return (this.Project.updateOne = sinon.stub().callsArgWith(2, null))
|
this.Project.updateOne = sinon.stub().callsArgWith(2, null)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call Project.updateOne', function (done) {
|
it('should call Project.updateOne', function (done) {
|
||||||
return this.TokenAccessHandler.addReadAndWriteUserToProject(
|
this.TokenAccessHandler.addReadAndWriteUserToProject(
|
||||||
this.userId,
|
this.userId,
|
||||||
this.projectId,
|
this.projectId,
|
||||||
err => {
|
err => {
|
||||||
|
expect(err).to.not.exist
|
||||||
expect(this.Project.updateOne.callCount).to.equal(1)
|
expect(this.Project.updateOne.callCount).to.equal(1)
|
||||||
expect(
|
expect(
|
||||||
this.Project.updateOne.calledWith({
|
this.Project.updateOne.calledWith({
|
||||||
|
@ -203,36 +193,36 @@ describe('TokenAccessHandler', function () {
|
||||||
'project-joined',
|
'project-joined',
|
||||||
{ mode: 'read-write' }
|
{ mode: 'read-write' }
|
||||||
)
|
)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not produce an error', function (done) {
|
it('should not produce an error', function (done) {
|
||||||
return this.TokenAccessHandler.addReadAndWriteUserToProject(
|
this.TokenAccessHandler.addReadAndWriteUserToProject(
|
||||||
this.userId,
|
this.userId,
|
||||||
this.projectId,
|
this.projectId,
|
||||||
err => {
|
err => {
|
||||||
expect(err).to.not.exist
|
expect(err).to.not.exist
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when Project.updateOne produces an error', function () {
|
describe('when Project.updateOne produces an error', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
return (this.Project.updateOne = sinon
|
this.Project.updateOne = sinon
|
||||||
.stub()
|
.stub()
|
||||||
.callsArgWith(2, new Error('woops')))
|
.callsArgWith(2, new Error('woops'))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should produce an error', function (done) {
|
it('should produce an error', function (done) {
|
||||||
return this.TokenAccessHandler.addReadAndWriteUserToProject(
|
this.TokenAccessHandler.addReadAndWriteUserToProject(
|
||||||
this.userId,
|
this.userId,
|
||||||
this.projectId,
|
this.projectId,
|
||||||
err => {
|
err => {
|
||||||
expect(err).to.exist
|
expect(err).to.exist
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -241,7 +231,7 @@ describe('TokenAccessHandler', function () {
|
||||||
|
|
||||||
describe('grantSessionTokenAccess', function () {
|
describe('grantSessionTokenAccess', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
return (this.req = { session: {}, headers: {} })
|
this.req = { session: {}, headers: {} }
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should add the token to the session', function (done) {
|
it('should add the token to the session', function (done) {
|
||||||
|
@ -253,7 +243,7 @@ describe('TokenAccessHandler', function () {
|
||||||
expect(
|
expect(
|
||||||
this.req.session.anonTokenAccess[this.projectId.toString()]
|
this.req.session.anonTokenAccess[this.projectId.toString()]
|
||||||
).to.equal(this.token)
|
).to.equal(this.token)
|
||||||
return done()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -267,27 +257,28 @@ describe('TokenAccessHandler', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should try to find projects with both kinds of token', function (done) {
|
it('should try to find projects with both kinds of token', function (done) {
|
||||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
this.token,
|
this.token,
|
||||||
(err, allowed) => {
|
(err, allowed) => {
|
||||||
|
expect(err).to.not.exist
|
||||||
expect(
|
expect(
|
||||||
this.TokenAccessHandler.getProjectByToken.callCount
|
this.TokenAccessHandler.getProjectByToken.callCount
|
||||||
).to.equal(1)
|
).to.equal(1)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should allow read-only access', function (done) {
|
it('should allow read-only access', function (done) {
|
||||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
this.token,
|
this.token,
|
||||||
(err, rw, ro) => {
|
(err, rw, ro) => {
|
||||||
expect(err).to.not.exist
|
expect(err).to.not.exist
|
||||||
expect(rw).to.equal(false)
|
expect(rw).to.equal(false)
|
||||||
expect(ro).to.equal(true)
|
expect(ro).to.equal(true)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -309,27 +300,28 @@ describe('TokenAccessHandler', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should try to find projects with both kinds of token', function (done) {
|
it('should try to find projects with both kinds of token', function (done) {
|
||||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
this.token,
|
this.token,
|
||||||
(err, rw, ro) => {
|
(err, rw, ro) => {
|
||||||
|
expect(err).to.not.exist
|
||||||
expect(
|
expect(
|
||||||
this.TokenAccessHandler.getProjectByToken.callCount
|
this.TokenAccessHandler.getProjectByToken.callCount
|
||||||
).to.equal(1)
|
).to.equal(1)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not allow read-and-write access', function (done) {
|
it('should not allow read-and-write access', function (done) {
|
||||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
this.token,
|
this.token,
|
||||||
(err, rw, ro) => {
|
(err, rw, ro) => {
|
||||||
expect(err).to.not.exist
|
expect(err).to.not.exist
|
||||||
expect(rw).to.equal(false)
|
expect(rw).to.equal(false)
|
||||||
expect(ro).to.equal(false)
|
expect(ro).to.equal(false)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -341,27 +333,28 @@ describe('TokenAccessHandler', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should try to find projects with both kinds of token', function (done) {
|
it('should try to find projects with both kinds of token', function (done) {
|
||||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
this.token,
|
this.token,
|
||||||
(err, rw, ro) => {
|
(err, rw, ro) => {
|
||||||
|
expect(err).to.not.exist
|
||||||
expect(
|
expect(
|
||||||
this.TokenAccessHandler.getProjectByToken.callCount
|
this.TokenAccessHandler.getProjectByToken.callCount
|
||||||
).to.equal(1)
|
).to.equal(1)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should allow read-and-write access', function (done) {
|
it('should allow read-and-write access', function (done) {
|
||||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
this.token,
|
this.token,
|
||||||
(err, rw, ro) => {
|
(err, rw, ro) => {
|
||||||
expect(err).to.not.exist
|
expect(err).to.not.exist
|
||||||
expect(rw).to.equal(true)
|
expect(rw).to.equal(true)
|
||||||
expect(ro).to.equal(false)
|
expect(ro).to.equal(false)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -376,27 +369,28 @@ describe('TokenAccessHandler', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should try to find projects with both kinds of token', function (done) {
|
it('should try to find projects with both kinds of token', function (done) {
|
||||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
this.token,
|
this.token,
|
||||||
(err, allowed) => {
|
(err, allowed) => {
|
||||||
|
expect(err).to.not.exist
|
||||||
expect(
|
expect(
|
||||||
this.TokenAccessHandler.getProjectByToken.callCount
|
this.TokenAccessHandler.getProjectByToken.callCount
|
||||||
).to.equal(1)
|
).to.equal(1)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not allow any access', function (done) {
|
it('should not allow any access', function (done) {
|
||||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
this.token,
|
this.token,
|
||||||
(err, rw, ro) => {
|
(err, rw, ro) => {
|
||||||
expect(err).to.not.exist
|
expect(err).to.not.exist
|
||||||
expect(rw).to.equal(false)
|
expect(rw).to.equal(false)
|
||||||
expect(ro).to.equal(false)
|
expect(ro).to.equal(false)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -410,20 +404,22 @@ describe('TokenAccessHandler', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should try to find projects with both kinds of token', function (done) {
|
it('should try to find projects with both kinds of token', function (done) {
|
||||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
this.token,
|
this.token,
|
||||||
(err, allowed) => {
|
(err, allowed) => {
|
||||||
|
expect(err).to.exist
|
||||||
|
expect(allowed).to.not.exist
|
||||||
expect(
|
expect(
|
||||||
this.TokenAccessHandler.getProjectByToken.callCount
|
this.TokenAccessHandler.getProjectByToken.callCount
|
||||||
).to.equal(1)
|
).to.equal(1)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should produce an error and not allow access', function (done) {
|
it('should produce an error and not allow access', function (done) {
|
||||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
this.token,
|
this.token,
|
||||||
(err, rw, ro) => {
|
(err, rw, ro) => {
|
||||||
|
@ -431,7 +427,7 @@ describe('TokenAccessHandler', function () {
|
||||||
expect(err).to.be.instanceof(Error)
|
expect(err).to.be.instanceof(Error)
|
||||||
expect(rw).to.equal(undefined)
|
expect(rw).to.equal(undefined)
|
||||||
expect(ro).to.equal(undefined)
|
expect(ro).to.equal(undefined)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -439,7 +435,7 @@ describe('TokenAccessHandler', function () {
|
||||||
|
|
||||||
describe('when project is not set to token-based access', function () {
|
describe('when project is not set to token-based access', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
return (this.project.publicAccesLevel = 'private')
|
this.project.publicAccesLevel = 'private'
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('for read-and-write project', function () {
|
describe('for read-and-write project', function () {
|
||||||
|
@ -453,14 +449,14 @@ describe('TokenAccessHandler', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not allow any access', function (done) {
|
it('should not allow any access', function (done) {
|
||||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
this.token,
|
this.token,
|
||||||
(err, rw, ro) => {
|
(err, rw, ro) => {
|
||||||
expect(err).to.not.exist
|
expect(err).to.not.exist
|
||||||
expect(rw).to.equal(false)
|
expect(rw).to.equal(false)
|
||||||
expect(ro).to.equal(false)
|
expect(ro).to.equal(false)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -477,14 +473,14 @@ describe('TokenAccessHandler', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not allow any access', function (done) {
|
it('should not allow any access', function (done) {
|
||||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
this.token,
|
this.token,
|
||||||
(err, rw, ro) => {
|
(err, rw, ro) => {
|
||||||
expect(err).to.not.exist
|
expect(err).to.not.exist
|
||||||
expect(rw).to.equal(false)
|
expect(rw).to.equal(false)
|
||||||
expect(ro).to.equal(false)
|
expect(ro).to.equal(false)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -498,14 +494,14 @@ describe('TokenAccessHandler', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not allow any access', function (done) {
|
it('should not allow any access', function (done) {
|
||||||
return this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
this.TokenAccessHandler.validateTokenForAnonymousAccess(
|
||||||
this.projectId,
|
this.projectId,
|
||||||
null,
|
null,
|
||||||
(err, rw, ro) => {
|
(err, rw, ro) => {
|
||||||
expect(err).to.not.exist
|
expect(err).to.not.exist
|
||||||
expect(rw).to.equal(false)
|
expect(rw).to.equal(false)
|
||||||
expect(ro).to.equal(false)
|
expect(ro).to.equal(false)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -515,21 +511,18 @@ describe('TokenAccessHandler', function () {
|
||||||
|
|
||||||
describe('getDocPublishedInfo', function () {
|
describe('getDocPublishedInfo', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
return (this.callback = sinon.stub())
|
this.callback = sinon.stub()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when v1 api not set', function () {
|
describe('when v1 api not set', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.settings.apis = { v1: undefined }
|
this.settings.apis = { v1: undefined }
|
||||||
return this.TokenAccessHandler.getV1DocPublishedInfo(
|
this.TokenAccessHandler.getV1DocPublishedInfo(this.token, this.callback)
|
||||||
this.token,
|
|
||||||
this.callback
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not check access and return default info', function () {
|
it('should not check access and return default info', function () {
|
||||||
expect(this.V1Api.request.called).to.equal(false)
|
expect(this.V1Api.request.called).to.equal(false)
|
||||||
return expect(
|
expect(
|
||||||
this.callback.calledWith(null, {
|
this.callback.calledWith(null, {
|
||||||
allow: true,
|
allow: true,
|
||||||
})
|
})
|
||||||
|
@ -539,7 +532,7 @@ describe('TokenAccessHandler', function () {
|
||||||
|
|
||||||
describe('when v1 api is set', function () {
|
describe('when v1 api is set', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
return (this.settings.apis = { v1: { url: 'v1Url' } })
|
this.settings.apis = { v1: { url: 'v1Url' } }
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('on V1Api.request success', function () {
|
describe('on V1Api.request success', function () {
|
||||||
|
@ -547,7 +540,7 @@ describe('TokenAccessHandler', function () {
|
||||||
this.V1Api.request = sinon
|
this.V1Api.request = sinon
|
||||||
.stub()
|
.stub()
|
||||||
.callsArgWith(1, null, null, 'mock-data')
|
.callsArgWith(1, null, null, 'mock-data')
|
||||||
return this.TokenAccessHandler.getV1DocPublishedInfo(
|
this.TokenAccessHandler.getV1DocPublishedInfo(
|
||||||
this.token,
|
this.token,
|
||||||
this.callback
|
this.callback
|
||||||
)
|
)
|
||||||
|
@ -559,23 +552,21 @@ describe('TokenAccessHandler', function () {
|
||||||
url: `/api/v1/sharelatex/docs/${this.token}/is_published`,
|
url: `/api/v1/sharelatex/docs/${this.token}/is_published`,
|
||||||
})
|
})
|
||||||
).to.equal(true)
|
).to.equal(true)
|
||||||
return expect(this.callback.calledWith(null, 'mock-data')).to.equal(
|
expect(this.callback.calledWith(null, 'mock-data')).to.equal(true)
|
||||||
true
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('on V1Api.request error', function () {
|
describe('on V1Api.request error', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.V1Api.request = sinon.stub().callsArgWith(1, 'error')
|
this.V1Api.request = sinon.stub().callsArgWith(1, 'error')
|
||||||
return this.TokenAccessHandler.getV1DocPublishedInfo(
|
this.TokenAccessHandler.getV1DocPublishedInfo(
|
||||||
this.token,
|
this.token,
|
||||||
this.callback
|
this.callback
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should callback with error', function () {
|
it('should callback with error', function () {
|
||||||
return expect(this.callback.calledWith('error')).to.equal(true)
|
expect(this.callback.calledWith('error')).to.equal(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -583,12 +574,12 @@ describe('TokenAccessHandler', function () {
|
||||||
|
|
||||||
describe('getV1DocInfo', function () {
|
describe('getV1DocInfo', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
return (this.callback = sinon.stub())
|
this.callback = sinon.stub()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when v1 api not set', function () {
|
describe('when v1 api not set', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
return this.TokenAccessHandler.getV1DocInfo(
|
this.TokenAccessHandler.getV1DocInfo(
|
||||||
this.token,
|
this.token,
|
||||||
this.v2UserId,
|
this.v2UserId,
|
||||||
this.callback
|
this.callback
|
||||||
|
@ -597,7 +588,7 @@ describe('TokenAccessHandler', function () {
|
||||||
|
|
||||||
it('should not check access and return default info', function () {
|
it('should not check access and return default info', function () {
|
||||||
expect(this.V1Api.request.called).to.equal(false)
|
expect(this.V1Api.request.called).to.equal(false)
|
||||||
return expect(
|
expect(
|
||||||
this.callback.calledWith(null, {
|
this.callback.calledWith(null, {
|
||||||
exists: true,
|
exists: true,
|
||||||
exported: false,
|
exported: false,
|
||||||
|
@ -608,7 +599,7 @@ describe('TokenAccessHandler', function () {
|
||||||
|
|
||||||
describe('when v1 api is set', function () {
|
describe('when v1 api is set', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
return (this.settings.apis = { v1: 'v1' })
|
this.settings.apis = { v1: 'v1' }
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('on V1Api.request success', function () {
|
describe('on V1Api.request success', function () {
|
||||||
|
@ -616,7 +607,7 @@ describe('TokenAccessHandler', function () {
|
||||||
this.V1Api.request = sinon
|
this.V1Api.request = sinon
|
||||||
.stub()
|
.stub()
|
||||||
.callsArgWith(1, null, null, 'mock-data')
|
.callsArgWith(1, null, null, 'mock-data')
|
||||||
return this.TokenAccessHandler.getV1DocInfo(
|
this.TokenAccessHandler.getV1DocInfo(
|
||||||
this.token,
|
this.token,
|
||||||
this.v2UserId,
|
this.v2UserId,
|
||||||
this.callback
|
this.callback
|
||||||
|
@ -629,16 +620,14 @@ describe('TokenAccessHandler', function () {
|
||||||
url: `/api/v1/sharelatex/docs/${this.token}/info`,
|
url: `/api/v1/sharelatex/docs/${this.token}/info`,
|
||||||
})
|
})
|
||||||
).to.equal(true)
|
).to.equal(true)
|
||||||
return expect(this.callback.calledWith(null, 'mock-data')).to.equal(
|
expect(this.callback.calledWith(null, 'mock-data')).to.equal(true)
|
||||||
true
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('on V1Api.request error', function () {
|
describe('on V1Api.request error', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.V1Api.request = sinon.stub().callsArgWith(1, 'error')
|
this.V1Api.request = sinon.stub().callsArgWith(1, 'error')
|
||||||
return this.TokenAccessHandler.getV1DocInfo(
|
this.TokenAccessHandler.getV1DocInfo(
|
||||||
this.token,
|
this.token,
|
||||||
this.v2UserId,
|
this.v2UserId,
|
||||||
this.callback
|
this.callback
|
||||||
|
@ -646,9 +635,67 @@ describe('TokenAccessHandler', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should callback with error', function () {
|
it('should callback with error', function () {
|
||||||
return expect(this.callback.calledWith('error')).to.equal(true)
|
expect(this.callback.calledWith('error')).to.equal(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('createTokenHashPrefix', function () {
|
||||||
|
it('creates a prefix of the hash', function () {
|
||||||
|
const prefix =
|
||||||
|
this.TokenAccessHandler.createTokenHashPrefix('zxpxjrwdtsgd')
|
||||||
|
expect(prefix.length).to.equal(6)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('checkTokenHashPrefix', function () {
|
||||||
|
it('sends "match" to metrics when prefix matches the prefix of the hash of the token', function () {
|
||||||
|
const token = 'zxpxjrwdtsgd'
|
||||||
|
const prefix = this.TokenAccessHandler.createTokenHashPrefix(token)
|
||||||
|
|
||||||
|
this.TokenAccessHandler.checkTokenHashPrefix(token, prefix, 'readOnly')
|
||||||
|
|
||||||
|
expect(this.Metrics.inc).to.have.been.calledWith(
|
||||||
|
'link-sharing.hash-check',
|
||||||
|
{
|
||||||
|
path: 'readOnly',
|
||||||
|
status: 'match',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('sends "mismatch" to metrics when prefix does not match the prefix of the hash of the token', function () {
|
||||||
|
const token = 'zxpxjrwdtsgd'
|
||||||
|
const prefix = this.TokenAccessHandler.createTokenHashPrefix(token)
|
||||||
|
|
||||||
|
this.TokenAccessHandler.checkTokenHashPrefix(
|
||||||
|
'anothertoken',
|
||||||
|
prefix,
|
||||||
|
'readOnly'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(this.Metrics.inc).to.have.been.calledWith(
|
||||||
|
'link-sharing.hash-check',
|
||||||
|
{
|
||||||
|
path: 'readOnly',
|
||||||
|
status: 'mismatch',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('sends "missing" to metrics when prefix is undefined', function () {
|
||||||
|
this.TokenAccessHandler.checkTokenHashPrefix(
|
||||||
|
'anothertoken',
|
||||||
|
undefined,
|
||||||
|
'readOnly'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(this.Metrics.inc).to.have.been.calledWith(
|
||||||
|
'link-sharing.hash-check',
|
||||||
|
{
|
||||||
|
path: 'readOnly',
|
||||||
|
status: 'missing',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue