Merge pull request #16979 from overleaf/jpa-join-project-remove-sl-1

[misc] joinProject: pass userId and anonymous access token in body 1/2

GitOrigin-RevId: 5d7832246c7262c004c2cd465d62488384b35ee3
This commit is contained in:
Jakob Ackermann 2024-02-09 11:26:16 +00:00 committed by Copybot
parent 19ba6c6a15
commit 974069bf1c
9 changed files with 165 additions and 6 deletions

2
package-lock.json generated
View file

@ -45571,6 +45571,7 @@
"mocha": "^10.2.0", "mocha": "^10.2.0",
"sandboxed-module": "~0.3.0", "sandboxed-module": "~0.3.0",
"sinon": "^9.2.4", "sinon": "^9.2.4",
"sinon-chai": "^3.7.0",
"timekeeper": "0.0.4", "timekeeper": "0.0.4",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"uid-safe": "^2.1.5" "uid-safe": "^2.1.5"
@ -54587,6 +54588,7 @@
"request": "^2.88.2", "request": "^2.88.2",
"sandboxed-module": "~0.3.0", "sandboxed-module": "~0.3.0",
"sinon": "^9.2.4", "sinon": "^9.2.4",
"sinon-chai": "^3.7.0",
"socket.io": "github:overleaf/socket.io#0.9.19-overleaf-10", "socket.io": "github:overleaf/socket.io#0.9.19-overleaf-10",
"socket.io-client": "github:overleaf/socket.io-client#0.9.17-overleaf-5", "socket.io-client": "github:overleaf/socket.io-client#0.9.17-overleaf-5",
"timekeeper": "0.0.4", "timekeeper": "0.0.4",

View file

@ -27,7 +27,10 @@ module.exports = {
pass: settings.apis.web.pass, pass: settings.apis.web.pass,
sendImmediately: true, sendImmediately: true,
}, },
json: true, json: {
userId,
anonymousAccessToken: user.anonymousAccessToken,
},
jar: false, jar: false,
headers, headers,
}, },

View file

@ -44,6 +44,7 @@
"mocha": "^10.2.0", "mocha": "^10.2.0",
"sandboxed-module": "~0.3.0", "sandboxed-module": "~0.3.0",
"sinon": "^9.2.4", "sinon": "^9.2.4",
"sinon-chai": "^3.7.0",
"timekeeper": "0.0.4", "timekeeper": "0.0.4",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"uid-safe": "^2.1.5" "uid-safe": "^2.1.5"

View file

@ -1,9 +1,13 @@
const chai = require('chai') const chai = require('chai')
const SandboxedModule = require('sandboxed-module') const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon') const sinon = require('sinon')
const chaiAsPromised = require('chai-as-promised')
const sinonChai = require('sinon-chai')
// Chai configuration // Chai configuration
chai.should() chai.should()
chai.use(chaiAsPromised)
chai.use(sinonChai)
// Global stubs // Global stubs
const sandbox = sinon.createSandbox() const sandbox = sinon.createSandbox()

View file

@ -68,7 +68,10 @@ describe('WebApiManager', function () {
pass: this.settings.apis.web.pass, pass: this.settings.apis.web.pass,
sendImmediately: true, sendImmediately: true,
}, },
json: true, json: {
userId: this.user_id,
anonymousAccessToken: undefined,
},
jar: false, jar: false,
headers: {}, headers: {},
}) })
@ -91,6 +94,65 @@ describe('WebApiManager', function () {
}) })
}) })
describe('with anon user', function () {
beforeEach(function () {
this.user_id = 'anonymous-user'
this.token = 'a-ro-token'
this.user = {
_id: this.user_id,
anonymousAccessToken: this.token,
}
this.response = {
project: { name: 'Test project' },
privilegeLevel: 'readOnly',
isRestrictedUser: true,
isTokenMember: false,
isInvitedMember: false,
}
this.request.post = sinon
.stub()
.yields(null, { statusCode: 200 }, this.response)
this.WebApiManager.joinProject(
this.project_id,
this.user,
this.callback
)
})
it('should send a request to web to join the project', function () {
this.request.post.should.have.been.calledWith({
url: `${this.settings.apis.web.url}/project/${this.project_id}/join`,
qs: {
user_id: this.user_id,
},
auth: {
user: this.settings.apis.web.user,
pass: this.settings.apis.web.pass,
sendImmediately: true,
},
json: {
userId: this.user_id,
anonymousAccessToken: this.token,
},
jar: false,
headers: { 'x-sl-anonymous-access-token': this.token },
})
})
it('should return the project, privilegeLevel, and restricted flag', function () {
this.callback.should.have.been.calledWith(
null,
this.response.project,
this.response.privilegeLevel,
{
isRestrictedUser: this.response.isRestrictedUser,
isTokenMember: this.response.isTokenMember,
isInvitedMember: this.response.isInvitedMember,
}
)
})
})
describe('when web replies with a 403', function () { describe('when web replies with a 403', function () {
beforeEach(function () { beforeEach(function () {
this.request.post = sinon this.request.post = sinon

View file

@ -57,7 +57,7 @@ const unsupportedSpellcheckLanguages = [
async function joinProject(req, res, next) { async function joinProject(req, res, next) {
const projectId = req.params.Project_id const projectId = req.params.Project_id
let userId = req.query.user_id // keep schema in sync with router let userId = req.body.userId || req.query.user_id // keep schema in sync with router
if (userId === 'anonymous-user') { if (userId === 'anonymous-user') {
userId = null userId = null
} }
@ -177,7 +177,8 @@ async function _buildJoinProjectView(req, projectId, userId) {
await CollaboratorsGetter.promises.getInvitedMembersWithPrivilegeLevels( await CollaboratorsGetter.promises.getInvitedMembersWithPrivilegeLevels(
projectId projectId
) )
const token = req.headers['x-sl-anonymous-access-token'] const token =
req.body.anonymousAccessToken || req.headers['x-sl-anonymous-access-token']
const privilegeLevel = const privilegeLevel =
await AuthorizationManager.promises.getPrivilegeLevelForProject( await AuthorizationManager.promises.getPrivilegeLevelForProject(
userId, userId,

View file

@ -71,7 +71,7 @@ module.exports = {
RateLimiterMiddleware.rateLimit(rateLimiters.joinProject, { RateLimiterMiddleware.rateLimit(rateLimiters.joinProject, {
params: ['Project_id'], params: ['Project_id'],
// keep schema in sync with controller // keep schema in sync with controller
getUserId: req => req.query.user_id, getUserId: req => req.body.userId || req.query.user_id,
}), }),
EditorHttpController.joinProject EditorHttpController.joinProject
) )

View file

@ -160,7 +160,15 @@ const _doTryTokenAccept = (
}) })
} }
const tryContentAccess = (user, projcetId, test, callback) => { const tryContentAccess = (user, projectId, test, callback) => {
tryContentAccessQuery(user, projectId, test, err1 => {
tryContentAccessBody(user, projectId, test, err2 => {
callback(err1 || err2)
})
})
}
const tryContentAccessQuery = (user, projcetId, test, callback) => {
// The real-time service calls this end point to determine the user's // The real-time service calls this end point to determine the user's
// permissions. // permissions.
let userId let userId
@ -191,7 +199,47 @@ const tryContentAccess = (user, projcetId, test, callback) => {
) )
} }
const tryContentAccessBody = (user, projcetId, test, callback) => {
// The real-time service calls this end point to determine the user's
// permissions.
let userId
if (user.id != null) {
userId = user.id
} else {
userId = 'anonymous-user'
}
request.post(
{
url: `/project/${projcetId}/join`,
auth: {
user: settings.apis.web.user,
pass: settings.apis.web.pass,
sendImmediately: true,
},
json: {
userId,
},
jar: false,
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
test(response, body)
callback()
}
)
}
const tryAnonContentAccess = (user, projectId, token, test, callback) => { const tryAnonContentAccess = (user, projectId, token, test, callback) => {
tryAnonContentAccessHeader(user, projectId, token, test, err1 => {
tryAnonContentAccessBody(user, projectId, token, test, err2 => {
callback(err1 || err2)
})
})
}
const tryAnonContentAccessHeader = (user, projectId, token, test, callback) => {
// The real-time service calls this end point to determine the user's // The real-time service calls this end point to determine the user's
// permissions. // permissions.
let userId let userId
@ -225,6 +273,39 @@ const tryAnonContentAccess = (user, projectId, token, test, callback) => {
) )
} }
const tryAnonContentAccessBody = (user, projectId, token, test, callback) => {
// The real-time service calls this end point to determine the user's
// permissions.
let userId
if (user.id != null) {
userId = user.id
} else {
userId = 'anonymous-user'
}
request.post(
{
url: `/project/${projectId}/join`,
auth: {
user: settings.apis.web.user,
pass: settings.apis.web.pass,
sendImmediately: true,
},
json: {
userId,
anonymousAccessToken: token,
},
jar: false,
},
(error, response, body) => {
if (error != null) {
return callback(error)
}
test(response, body)
callback()
}
)
}
const tryFetchProjectTokens = (user, projectId, callback) => { const tryFetchProjectTokens = (user, projectId, callback) => {
user.request.get( user.request.get(
{ url: `/project/${projectId}/tokens`, json: true }, { url: `/project/${projectId}/tokens`, json: true },

View file

@ -167,6 +167,7 @@ describe('EditorHttpController', function () {
beforeEach(function () { beforeEach(function () {
this.req.params = { Project_id: this.project._id } this.req.params = { Project_id: this.project._id }
this.req.query = { user_id: this.user._id } this.req.query = { user_id: this.user._id }
this.req.body = { userId: this.user._id }
}) })
describe('successfully', function () { describe('successfully', function () {
@ -251,6 +252,10 @@ describe('EditorHttpController', function () {
beforeEach(function (done) { beforeEach(function (done) {
this.token = 'token' this.token = 'token'
this.TokenAccessHandler.getRequestToken.returns(this.token) this.TokenAccessHandler.getRequestToken.returns(this.token)
this.req.body = {
userId: 'anonymous-user',
anonymousAccessToken: this.token,
}
this.req.query = { user_id: 'anonymous-user' } this.req.query = { user_id: 'anonymous-user' }
this.req.headers = { 'x-sl-anonymous-access-token': this.token } this.req.headers = { 'x-sl-anonymous-access-token': this.token }
this.res.callback = done this.res.callback = done