[misc] synchronous client store using an Object at .ol_context

This commit is contained in:
Jakob Ackermann 2020-02-24 13:32:20 +00:00
parent f2cae26166
commit 5282f8f531
10 changed files with 113 additions and 177 deletions

View file

@ -6,13 +6,10 @@ module.exports = AuthorizationManager =
AuthorizationManager._assertClientHasPrivilegeLevel client, ["readAndWrite", "owner"], callback AuthorizationManager._assertClientHasPrivilegeLevel client, ["readAndWrite", "owner"], callback
_assertClientHasPrivilegeLevel: (client, allowedLevels, callback = (error) ->) -> _assertClientHasPrivilegeLevel: (client, allowedLevels, callback = (error) ->) ->
client.get "privilege_level", (error, privilegeLevel) -> if client.ol_context["privilege_level"] in allowedLevels
return callback(error) if error? callback null
allowed = (privilegeLevel in allowedLevels) else
if allowed callback new Error("not authorized")
callback null
else
callback new Error("not authorized")
assertClientCanViewProjectAndDoc: (client, doc_id, callback = (error) ->) -> assertClientCanViewProjectAndDoc: (client, doc_id, callback = (error) ->) ->
AuthorizationManager.assertClientCanViewProject client, (error) -> AuthorizationManager.assertClientCanViewProject client, (error) ->
@ -25,15 +22,15 @@ module.exports = AuthorizationManager =
AuthorizationManager._assertClientCanAccessDoc client, doc_id, callback AuthorizationManager._assertClientCanAccessDoc client, doc_id, callback
_assertClientCanAccessDoc: (client, doc_id, callback = (error) ->) -> _assertClientCanAccessDoc: (client, doc_id, callback = (error) ->) ->
client.get "doc:#{doc_id}", (error, status) -> if client.ol_context["doc:#{doc_id}"] is "allowed"
return callback(error) if error? callback null
if status? and status is "allowed" else
callback null callback new Error("not authorized")
else
callback new Error("not authorized")
addAccessToDoc: (client, doc_id, callback = (error) ->) -> addAccessToDoc: (client, doc_id, callback = (error) ->) ->
client.set("doc:#{doc_id}", "allowed", callback) client.ol_context["doc:#{doc_id}"] = "allowed"
callback(null)
removeAccessToDoc: (client, doc_id, callback = (error) ->) -> removeAccessToDoc: (client, doc_id, callback = (error) ->) ->
client.del("doc:#{doc_id}", callback) delete client.ol_context["doc:#{doc_id}"]
callback(null)

View file

@ -1,4 +1,3 @@
Utils = require "./Utils"
async = require "async" async = require "async"
module.exports = HttpController = module.exports = HttpController =
@ -8,11 +7,8 @@ module.exports = HttpController =
# and for checking internal state in acceptance tests. The acceptances tests # and for checking internal state in acceptance tests. The acceptances tests
# should provide appropriate coverage. # should provide appropriate coverage.
_getConnectedClientView: (ioClient, callback = (error, client) ->) -> _getConnectedClientView: (ioClient, callback = (error, client) ->) ->
client_id = ioClient.id client_id = ioClient.id
Utils.getClientAttributes ioClient, [ {project_id, user_id, first_name, last_name, email, connected_time} = ioClient.ol_context
"project_id", "user_id", "first_name", "last_name", "email", "connected_time"
], (error, {project_id, user_id, first_name, last_name, email, connected_time}) ->
return callback(error) if error?
client = {client_id, project_id, user_id, first_name, last_name, email, connected_time} client = {client_id, project_id, user_id, first_name, last_name, email, connected_time}
client.rooms = [] client.rooms = []
for name, joined of ioClient.manager.roomClients[client_id] for name, joined of ioClient.manager.roomClients[client_id]

View file

@ -4,7 +4,6 @@ settings = require "settings-sharelatex"
WebsocketController = require "./WebsocketController" WebsocketController = require "./WebsocketController"
HttpController = require "./HttpController" HttpController = require "./HttpController"
HttpApiController = require "./HttpApiController" HttpApiController = require "./HttpApiController"
Utils = require "./Utils"
bodyParser = require "body-parser" bodyParser = require "body-parser"
base64id = require("base64id") base64id = require("base64id")
@ -16,10 +15,9 @@ httpAuth = basicAuth (user, pass)->
return isValid return isValid
module.exports = Router = module.exports = Router =
_handleError: (callback = ((error) ->), error, client, method, extraAttrs = {}) -> _handleError: (callback = ((error) ->), error, client, method, attrs = {}) ->
Utils.getClientAttributes client, ["project_id", "doc_id", "user_id"], (_, attrs) -> for key in ["project_id", "doc_id", "user_id"]
for key, value of extraAttrs attrs[key] = client.ol_context[key]
attrs[key] = value
attrs.client_id = client.id attrs.client_id = client.id
attrs.err = error attrs.err = error
if error.name == "CodedError" if error.name == "CodedError"
@ -57,6 +55,8 @@ module.exports = Router =
app.post "/client/:client_id/disconnect", httpAuth, HttpApiController.disconnectClient app.post "/client/:client_id/disconnect", httpAuth, HttpApiController.disconnectClient
session.on 'connection', (error, client, session) -> session.on 'connection', (error, client, session) ->
client.ol_context = {}
client?.on "error", (err) -> client?.on "error", (err) ->
logger.err { clientErr: err }, "socket.io client error" logger.err { clientErr: err }, "socket.io client error"
if client.connected if client.connected
@ -112,9 +112,14 @@ module.exports = Router =
client.on "disconnect", () -> client.on "disconnect", () ->
metrics.inc('socket-io.disconnect') metrics.inc('socket-io.disconnect')
metrics.gauge('socket-io.clients', io.sockets.clients()?.length - 1) metrics.gauge('socket-io.clients', io.sockets.clients()?.length - 1)
cleanup = () ->
delete client.ol_context
WebsocketController.leaveProject io, client, (err) -> WebsocketController.leaveProject io, client, (err) ->
if err? if err?
Router._handleError null, err, client, "leaveProject" Router._handleError cleanup, err, client, "leaveProject"
else
cleanup()
# Variadic. The possible arguments: # Variadic. The possible arguments:
# doc_id, callback # doc_id, callback

View file

@ -1,14 +0,0 @@
async = require "async"
module.exports = Utils =
getClientAttributes: (client, keys, callback = (error, attributes) ->) ->
attributes = {}
jobs = keys.map (key) ->
(callback) ->
client.get key, (error, value) ->
return callback(error) if error?
attributes[key] = value
callback()
async.series jobs, (error) ->
return callback(error) if error?
callback null, attributes

View file

@ -7,7 +7,6 @@ DocumentUpdaterManager = require "./DocumentUpdaterManager"
ConnectedUsersManager = require "./ConnectedUsersManager" ConnectedUsersManager = require "./ConnectedUsersManager"
WebsocketLoadBalancer = require "./WebsocketLoadBalancer" WebsocketLoadBalancer = require "./WebsocketLoadBalancer"
RoomManager = require "./RoomManager" RoomManager = require "./RoomManager"
Utils = require "./Utils"
module.exports = WebsocketController = module.exports = WebsocketController =
# If the protocol version changes when the client reconnects, # If the protocol version changes when the client reconnects,
@ -34,17 +33,17 @@ module.exports = WebsocketController =
logger.warn {err, project_id, user_id, client_id: client.id}, "user is not authorized to join project" logger.warn {err, project_id, user_id, client_id: client.id}, "user is not authorized to join project"
return callback(err) return callback(err)
client.set("privilege_level", privilegeLevel) client.ol_context["privilege_level"] = privilegeLevel
client.set("user_id", user_id) client.ol_context["user_id"] = user_id
client.set("project_id", project_id) client.ol_context["project_id"] = project_id
client.set("owner_id", project?.owner?._id) client.ol_context["owner_id"] = project?.owner?._id
client.set("first_name", user?.first_name) client.ol_context["first_name"] = user?.first_name
client.set("last_name", user?.last_name) client.ol_context["last_name"] = user?.last_name
client.set("email", user?.email) client.ol_context["email"] = user?.email
client.set("connected_time", new Date()) client.ol_context["connected_time"] = new Date()
client.set("signup_date", user?.signUpDate) client.ol_context["signup_date"] = user?.signUpDate
client.set("login_count", user?.loginCount) client.ol_context["login_count"] = user?.loginCount
client.set("is_restricted_user", !!(isRestrictedUser)) client.ol_context["is_restricted_user"] = !!(isRestrictedUser)
RoomManager.joinProject client, project_id, (err) -> RoomManager.joinProject client, project_id, (err) ->
return callback(err) if err return callback(err) if err
@ -59,8 +58,7 @@ module.exports = WebsocketController =
# is determined by FLUSH_IF_EMPTY_DELAY. # is determined by FLUSH_IF_EMPTY_DELAY.
FLUSH_IF_EMPTY_DELAY: 500 #ms FLUSH_IF_EMPTY_DELAY: 500 #ms
leaveProject: (io, client, callback = (error) ->) -> leaveProject: (io, client, callback = (error) ->) ->
Utils.getClientAttributes client, ["project_id", "user_id"], (error, {project_id, user_id}) -> {project_id, user_id} = client.ol_context
return callback(error) if error?
return callback() unless project_id # client did not join project return callback() unless project_id # client did not join project
metrics.inc "editor.leave-project" metrics.inc "editor.leave-project"
@ -84,13 +82,12 @@ module.exports = WebsocketController =
, WebsocketController.FLUSH_IF_EMPTY_DELAY , WebsocketController.FLUSH_IF_EMPTY_DELAY
joinDoc: (client, doc_id, fromVersion = -1, options, callback = (error, doclines, version, ops, ranges) ->) -> joinDoc: (client, doc_id, fromVersion = -1, options, callback = (error, doclines, version, ops, ranges) ->) ->
if client.disconnected if client.disconnected
metrics.inc('editor.join-doc.disconnected', 1, {status: 'immediately'}) metrics.inc('editor.join-doc.disconnected', 1, {status: 'immediately'})
return callback() return callback()
metrics.inc "editor.join-doc" metrics.inc "editor.join-doc"
Utils.getClientAttributes client, ["project_id", "user_id", "is_restricted_user"], (error, {project_id, user_id, is_restricted_user}) -> {project_id, user_id, is_restricted_user} = client.ol_context
return callback(error) if error?
return callback(new Error("no project_id found on client")) if !project_id? return callback(new Error("no project_id found on client")) if !project_id?
logger.log {user_id, project_id, doc_id, fromVersion, client_id: client.id}, "client joining doc" logger.log {user_id, project_id, doc_id, fromVersion, client_id: client.id}, "client joining doc"
@ -142,9 +139,9 @@ module.exports = WebsocketController =
callback null, escapedLines, version, ops, ranges callback null, escapedLines, version, ops, ranges
leaveDoc: (client, doc_id, callback = (error) ->) -> leaveDoc: (client, doc_id, callback = (error) ->) ->
# client may have disconnected, but we have to cleanup internal state. # client may have disconnected, but we have to cleanup internal state.
metrics.inc "editor.leave-doc" metrics.inc "editor.leave-doc"
Utils.getClientAttributes client, ["project_id", "user_id"], (error, {project_id, user_id}) -> {project_id, user_id} = client.ol_context
logger.log {user_id, project_id, doc_id, client_id: client.id}, "client leaving doc" logger.log {user_id, project_id, doc_id, client_id: client.id}, "client leaving doc"
RoomManager.leaveDoc(client, doc_id) RoomManager.leaveDoc(client, doc_id)
# we could remove permission when user leaves a doc, but because # we could remove permission when user leaves a doc, but because
@ -153,15 +150,12 @@ module.exports = WebsocketController =
## AuthorizationManager.removeAccessToDoc client, doc_id ## AuthorizationManager.removeAccessToDoc client, doc_id
callback() callback()
updateClientPosition: (client, cursorData, callback = (error) ->) -> updateClientPosition: (client, cursorData, callback = (error) ->) ->
if client.disconnected if client.disconnected
# do not create a ghost entry in redis # do not create a ghost entry in redis
return callback() return callback()
metrics.inc "editor.update-client-position", 0.1 metrics.inc "editor.update-client-position", 0.1
Utils.getClientAttributes client, [ {project_id, first_name, last_name, email, user_id} = client.ol_context
"project_id", "first_name", "last_name", "email", "user_id"
], (error, {project_id, first_name, last_name, email, user_id}) ->
return callback(error) if error?
logger.log {user_id, project_id, client_id: client.id, cursorData: cursorData}, "updating client position" logger.log {user_id, project_id, client_id: client.id, cursorData: cursorData}, "updating client position"
AuthorizationManager.assertClientCanViewProjectAndDoc client, cursorData.doc_id, (error) -> AuthorizationManager.assertClientCanViewProjectAndDoc client, cursorData.doc_id, (error) ->
@ -198,14 +192,12 @@ module.exports = WebsocketController =
CLIENT_REFRESH_DELAY: 1000 CLIENT_REFRESH_DELAY: 1000
getConnectedUsers: (client, callback = (error, users) ->) -> getConnectedUsers: (client, callback = (error, users) ->) ->
if client.disconnected if client.disconnected
# they are not interested anymore, skip the redis lookups # they are not interested anymore, skip the redis lookups
return callback() return callback()
metrics.inc "editor.get-connected-users" metrics.inc "editor.get-connected-users"
Utils.getClientAttributes client, ["project_id", "user_id", "is_restricted_user"], (error, clientAttributes) -> {project_id, user_id, is_restricted_user} = client.ol_context
return callback(error) if error?
{project_id, user_id, is_restricted_user} = clientAttributes
if is_restricted_user if is_restricted_user
return callback(null, []) return callback(null, [])
return callback(new Error("no project_id found on client")) if !project_id? return callback(new Error("no project_id found on client")) if !project_id?
@ -221,9 +213,8 @@ module.exports = WebsocketController =
, WebsocketController.CLIENT_REFRESH_DELAY , WebsocketController.CLIENT_REFRESH_DELAY
applyOtUpdate: (client, doc_id, update, callback = (error) ->) -> applyOtUpdate: (client, doc_id, update, callback = (error) ->) ->
# client may have disconnected, but we can submit their update to doc-updater anyways. # client may have disconnected, but we can submit their update to doc-updater anyways.
Utils.getClientAttributes client, ["user_id", "project_id"], (error, {user_id, project_id}) -> {user_id, project_id} = client.ol_context
return callback(error) if error?
return callback(new Error("no project_id found on client")) if !project_id? return callback(new Error("no project_id found on client")) if !project_id?
WebsocketController._assertClientCanApplyUpdate client, doc_id, update, (error) -> WebsocketController._assertClientCanApplyUpdate client, doc_id, update, (error) ->

View file

@ -7,7 +7,6 @@ HealthCheckManager = require "./HealthCheckManager"
RoomManager = require "./RoomManager" RoomManager = require "./RoomManager"
ChannelManager = require "./ChannelManager" ChannelManager = require "./ChannelManager"
ConnectedUsersManager = require "./ConnectedUsersManager" ConnectedUsersManager = require "./ConnectedUsersManager"
Utils = require './Utils'
Async = require 'async' Async = require 'async'
RESTRICTED_USER_MESSAGE_TYPE_PASS_LIST = [ RESTRICTED_USER_MESSAGE_TYPE_PASS_LIST = [
@ -94,8 +93,7 @@ module.exports = WebsocketLoadBalancer =
Async.eachLimit clientList Async.eachLimit clientList
, 2 , 2
, (client, cb) -> , (client, cb) ->
Utils.getClientAttributes client, ['is_restricted_user'], (err, {is_restricted_user}) -> is_restricted_user = client.ol_context['is_restricted_user']
return cb(err) if err?
if !seen[client.id] if !seen[client.id]
seen[client.id] = true seen[client.id] = true
if !(is_restricted_user && message.message not in RESTRICTED_USER_MESSAGE_TYPE_PASS_LIST) if !(is_restricted_user && message.message not in RESTRICTED_USER_MESSAGE_TYPE_PASS_LIST)

View file

@ -9,64 +9,56 @@ modulePath = '../../../app/js/AuthorizationManager'
describe 'AuthorizationManager', -> describe 'AuthorizationManager', ->
beforeEach -> beforeEach ->
@client = @client =
params: {} ol_context: {}
get: (param, cb) ->
cb null, @params[param]
set: (param, value, cb) ->
@params[param] = value
cb()
del: (param, cb) ->
delete @params[param]
cb()
@AuthorizationManager = SandboxedModule.require modulePath, requires: {} @AuthorizationManager = SandboxedModule.require modulePath, requires: {}
describe "assertClientCanViewProject", -> describe "assertClientCanViewProject", ->
it "should allow the readOnly privilegeLevel", (done) -> it "should allow the readOnly privilegeLevel", (done) ->
@client.params.privilege_level = "readOnly" @client.ol_context.privilege_level = "readOnly"
@AuthorizationManager.assertClientCanViewProject @client, (error) -> @AuthorizationManager.assertClientCanViewProject @client, (error) ->
expect(error).to.be.null expect(error).to.be.null
done() done()
it "should allow the readAndWrite privilegeLevel", (done) -> it "should allow the readAndWrite privilegeLevel", (done) ->
@client.params.privilege_level = "readAndWrite" @client.ol_context.privilege_level = "readAndWrite"
@AuthorizationManager.assertClientCanViewProject @client, (error) -> @AuthorizationManager.assertClientCanViewProject @client, (error) ->
expect(error).to.be.null expect(error).to.be.null
done() done()
it "should allow the owner privilegeLevel", (done) -> it "should allow the owner privilegeLevel", (done) ->
@client.params.privilege_level = "owner" @client.ol_context.privilege_level = "owner"
@AuthorizationManager.assertClientCanViewProject @client, (error) -> @AuthorizationManager.assertClientCanViewProject @client, (error) ->
expect(error).to.be.null expect(error).to.be.null
done() done()
it "should return an error with any other privilegeLevel", (done) -> it "should return an error with any other privilegeLevel", (done) ->
@client.params.privilege_level = "unknown" @client.ol_context.privilege_level = "unknown"
@AuthorizationManager.assertClientCanViewProject @client, (error) -> @AuthorizationManager.assertClientCanViewProject @client, (error) ->
error.message.should.equal "not authorized" error.message.should.equal "not authorized"
done() done()
describe "assertClientCanEditProject", -> describe "assertClientCanEditProject", ->
it "should not allow the readOnly privilegeLevel", (done) -> it "should not allow the readOnly privilegeLevel", (done) ->
@client.params.privilege_level = "readOnly" @client.ol_context.privilege_level = "readOnly"
@AuthorizationManager.assertClientCanEditProject @client, (error) -> @AuthorizationManager.assertClientCanEditProject @client, (error) ->
error.message.should.equal "not authorized" error.message.should.equal "not authorized"
done() done()
it "should allow the readAndWrite privilegeLevel", (done) -> it "should allow the readAndWrite privilegeLevel", (done) ->
@client.params.privilege_level = "readAndWrite" @client.ol_context.privilege_level = "readAndWrite"
@AuthorizationManager.assertClientCanEditProject @client, (error) -> @AuthorizationManager.assertClientCanEditProject @client, (error) ->
expect(error).to.be.null expect(error).to.be.null
done() done()
it "should allow the owner privilegeLevel", (done) -> it "should allow the owner privilegeLevel", (done) ->
@client.params.privilege_level = "owner" @client.ol_context.privilege_level = "owner"
@AuthorizationManager.assertClientCanEditProject @client, (error) -> @AuthorizationManager.assertClientCanEditProject @client, (error) ->
expect(error).to.be.null expect(error).to.be.null
done() done()
it "should return an error with any other privilegeLevel", (done) -> it "should return an error with any other privilegeLevel", (done) ->
@client.params.privilege_level = "unknown" @client.ol_context.privilege_level = "unknown"
@AuthorizationManager.assertClientCanEditProject @client, (error) -> @AuthorizationManager.assertClientCanEditProject @client, (error) ->
error.message.should.equal "not authorized" error.message.should.equal "not authorized"
done() done()
@ -77,11 +69,11 @@ describe 'AuthorizationManager', ->
beforeEach () -> beforeEach () ->
@doc_id = "12345" @doc_id = "12345"
@callback = sinon.stub() @callback = sinon.stub()
@client.params = {} @client.ol_context = {}
describe "when not authorised at the project level", -> describe "when not authorised at the project level", ->
beforeEach () -> beforeEach () ->
@client.params.privilege_level = "unknown" @client.ol_context.privilege_level = "unknown"
it "should not allow access", () -> it "should not allow access", () ->
@AuthorizationManager.assertClientCanViewProjectAndDoc @client, @doc_id, (err) -> @AuthorizationManager.assertClientCanViewProjectAndDoc @client, @doc_id, (err) ->
@ -97,7 +89,7 @@ describe 'AuthorizationManager', ->
describe "when authorised at the project level", -> describe "when authorised at the project level", ->
beforeEach () -> beforeEach () ->
@client.params.privilege_level = "readOnly" @client.ol_context.privilege_level = "readOnly"
describe "and not authorised at the document level", -> describe "and not authorised at the document level", ->
it "should not allow access", () -> it "should not allow access", () ->
@ -127,11 +119,11 @@ describe 'AuthorizationManager', ->
beforeEach () -> beforeEach () ->
@doc_id = "12345" @doc_id = "12345"
@callback = sinon.stub() @callback = sinon.stub()
@client.params = {} @client.ol_context = {}
describe "when not authorised at the project level", -> describe "when not authorised at the project level", ->
beforeEach () -> beforeEach () ->
@client.params.privilege_level = "readOnly" @client.ol_context.privilege_level = "readOnly"
it "should not allow access", () -> it "should not allow access", () ->
@AuthorizationManager.assertClientCanEditProjectAndDoc @client, @doc_id, (err) -> @AuthorizationManager.assertClientCanEditProjectAndDoc @client, @doc_id, (err) ->
@ -147,7 +139,7 @@ describe 'AuthorizationManager', ->
describe "when authorised at the project level", -> describe "when authorised at the project level", ->
beforeEach () -> beforeEach () ->
@client.params.privilege_level = "readAndWrite" @client.ol_context.privilege_level = "readAndWrite"
describe "and not authorised at the document level", -> describe "and not authorised at the document level", ->
it "should not allow access", () -> it "should not allow access", () ->

View file

@ -23,9 +23,7 @@ describe 'WebsocketController', ->
disconnected: false disconnected: false
id: @client_id = "mock-client-id-123" id: @client_id = "mock-client-id-123"
publicId: "other-id-#{Math.random()}" publicId: "other-id-#{Math.random()}"
params: {} ol_context: {}
set: sinon.stub()
get: (param, cb) -> cb null, @params[param]
join: sinon.stub() join: sinon.stub()
leave: sinon.stub() leave: sinon.stub()
@WebsocketController = SandboxedModule.require modulePath, requires: @WebsocketController = SandboxedModule.require modulePath, requires:
@ -69,38 +67,27 @@ describe 'WebsocketController', ->
@RoomManager.joinProject.calledWith(@client, @project_id).should.equal true @RoomManager.joinProject.calledWith(@client, @project_id).should.equal true
it "should set the privilege level on the client", -> it "should set the privilege level on the client", ->
@client.set.calledWith("privilege_level", @privilegeLevel).should.equal true @client.ol_context["privilege_level"].should.equal @privilegeLevel
it "should set the user's id on the client", -> it "should set the user's id on the client", ->
@client.set.calledWith("user_id", @user._id).should.equal true @client.ol_context["user_id"].should.equal @user._id
it "should set the user's email on the client", -> it "should set the user's email on the client", ->
@client.set.calledWith("email", @user.email).should.equal true @client.ol_context["email"].should.equal @user.email
it "should set the user's first_name on the client", -> it "should set the user's first_name on the client", ->
@client.set.calledWith("first_name", @user.first_name).should.equal true @client.ol_context["first_name"].should.equal @user.first_name
it "should set the user's last_name on the client", -> it "should set the user's last_name on the client", ->
@client.set.calledWith("last_name", @user.last_name).should.equal true @client.ol_context["last_name"].should.equal @user.last_name
it "should set the user's sign up date on the client", -> it "should set the user's sign up date on the client", ->
@client.set.calledWith("signup_date", @user.signUpDate).should.equal true @client.ol_context["signup_date"].should.equal @user.signUpDate
it "should set the user's login_count on the client", -> it "should set the user's login_count on the client", ->
@client.set.calledWith("login_count", @user.loginCount).should.equal true @client.ol_context["login_count"].should.equal @user.loginCount
it "should set the connected time on the client", -> it "should set the connected time on the client", ->
@client.set.calledWith("connected_time", new Date()).should.equal true @client.ol_context["connected_time"].should.equal new Date()
it "should set the project_id on the client", -> it "should set the project_id on the client", ->
@client.set.calledWith("project_id", @project_id).should.equal true @client.ol_context["project_id"].should.equal @project_id
it "should set the project owner id on the client", -> it "should set the project owner id on the client", ->
@client.set.calledWith("owner_id", @owner_id).should.equal true @client.ol_context["owner_id"].should.equal @owner_id
it "should set the is_restricted_user flag on the client", -> it "should set the is_restricted_user flag on the client", ->
@client.set.calledWith("is_restricted_user", @isRestrictedUser).should.equal true @client.ol_context["is_restricted_user"].should.equal @isRestrictedUser
it "should call the callback with the project, privilegeLevel and protocolVersion", -> it "should call the callback with the project, privilegeLevel and protocolVersion", ->
@callback @callback
.calledWith(null, @project, @privilegeLevel, @WebsocketController.PROTOCOL_VERSION) .calledWith(null, @project, @privilegeLevel, @WebsocketController.PROTOCOL_VERSION)
@ -191,14 +178,14 @@ describe 'WebsocketController', ->
if room_id != @project_id if room_id != @project_id
throw "expected room_id to be project_id" throw "expected room_id to be project_id"
return @clientsInRoom return @clientsInRoom
@client.params.project_id = @project_id @client.ol_context.project_id = @project_id
@client.params.user_id = @user_id @client.ol_context.user_id = @user_id
@WebsocketController.FLUSH_IF_EMPTY_DELAY = 0 @WebsocketController.FLUSH_IF_EMPTY_DELAY = 0
tk.reset() # Allow setTimeout to work. tk.reset() # Allow setTimeout to work.
describe "when the client did not joined a project yet", -> describe "when the client did not joined a project yet", ->
beforeEach (done) -> beforeEach (done) ->
@client.params = {} @client.ol_context = {}
@WebsocketController.leaveProject @io, @client, done @WebsocketController.leaveProject @io, @client, done
it "should bail out when calling leaveProject", () -> it "should bail out when calling leaveProject", () ->
@ -248,8 +235,8 @@ describe 'WebsocketController', ->
describe "when client has not authenticated", -> describe "when client has not authenticated", ->
beforeEach (done) -> beforeEach (done) ->
@client.params.user_id = null @client.ol_context.user_id = null
@client.params.project_id = null @client.ol_context.project_id = null
@WebsocketController.leaveProject @io, @client, done @WebsocketController.leaveProject @io, @client, done
it "should not end clientTracking.clientDisconnected to the project room", -> it "should not end clientTracking.clientDisconnected to the project room", ->
@ -272,8 +259,8 @@ describe 'WebsocketController', ->
describe "when client has not joined a project", -> describe "when client has not joined a project", ->
beforeEach (done) -> beforeEach (done) ->
@client.params.user_id = @user_id @client.ol_context.user_id = @user_id
@client.params.project_id = null @client.ol_context.project_id = null
@WebsocketController.leaveProject @io, @client, done @WebsocketController.leaveProject @io, @client, done
it "should not end clientTracking.clientDisconnected to the project room", -> it "should not end clientTracking.clientDisconnected to the project room", ->
@ -303,8 +290,8 @@ describe 'WebsocketController', ->
@ranges = { "mock": "ranges" } @ranges = { "mock": "ranges" }
@options = {} @options = {}
@client.params.project_id = @project_id @client.ol_context.project_id = @project_id
@client.params.is_restricted_user = false @client.ol_context.is_restricted_user = false
@AuthorizationManager.addAccessToDoc = sinon.stub() @AuthorizationManager.addAccessToDoc = sinon.stub()
@AuthorizationManager.assertClientCanViewProject = sinon.stub().callsArgWith(1, null) @AuthorizationManager.assertClientCanViewProject = sinon.stub().callsArgWith(1, null)
@DocumentUpdaterManager.getDocument = sinon.stub().callsArgWith(3, null, @doc_lines, @version, @ranges, @ops) @DocumentUpdaterManager.getDocument = sinon.stub().callsArgWith(3, null, @doc_lines, @version, @ranges, @ops)
@ -408,7 +395,7 @@ describe 'WebsocketController', ->
describe "with a restricted client", -> describe "with a restricted client", ->
beforeEach -> beforeEach ->
@ranges.comments = [{op: {a: 1}}, {op: {a: 2}}] @ranges.comments = [{op: {a: 1}}, {op: {a: 2}}]
@client.params.is_restricted_user = true @client.ol_context.is_restricted_user = true
@WebsocketController.joinDoc @client, @doc_id, -1, @options, @callback @WebsocketController.joinDoc @client, @doc_id, -1, @options, @callback
it "should overwrite ranges.comments with an empty list", -> it "should overwrite ranges.comments with an empty list", ->
@ -463,7 +450,7 @@ describe 'WebsocketController', ->
describe "leaveDoc", -> describe "leaveDoc", ->
beforeEach -> beforeEach ->
@doc_id = "doc-id-123" @doc_id = "doc-id-123"
@client.params.project_id = @project_id @client.ol_context.project_id = @project_id
@RoomManager.leaveDoc = sinon.stub() @RoomManager.leaveDoc = sinon.stub()
@WebsocketController.leaveDoc @client, @doc_id, @callback @WebsocketController.leaveDoc @client, @doc_id, @callback
@ -479,7 +466,7 @@ describe 'WebsocketController', ->
describe "getConnectedUsers", -> describe "getConnectedUsers", ->
beforeEach -> beforeEach ->
@client.params.project_id = @project_id @client.ol_context.project_id = @project_id
@users = ["mock", "users"] @users = ["mock", "users"]
@WebsocketLoadBalancer.emitToRoom = sinon.stub() @WebsocketLoadBalancer.emitToRoom = sinon.stub()
@ConnectedUsersManager.getConnectedUsers = sinon.stub().callsArgWith(1, null, @users) @ConnectedUsersManager.getConnectedUsers = sinon.stub().callsArgWith(1, null, @users)
@ -527,7 +514,7 @@ describe 'WebsocketController', ->
describe "when restricted user", -> describe "when restricted user", ->
beforeEach -> beforeEach ->
@client.params.is_restricted_user = true @client.ol_context.is_restricted_user = true
@AuthorizationManager.assertClientCanViewProject = sinon.stub().callsArgWith(1, null) @AuthorizationManager.assertClientCanViewProject = sinon.stub().callsArgWith(1, null)
@WebsocketController.getConnectedUsers @client, @callback @WebsocketController.getConnectedUsers @client, @callback
@ -564,14 +551,13 @@ describe 'WebsocketController', ->
describe "with a logged in user", -> describe "with a logged in user", ->
beforeEach -> beforeEach ->
@clientParams = { @client.ol_context = {
project_id: @project_id project_id: @project_id
first_name: @first_name = "Douglas" first_name: @first_name = "Douglas"
last_name: @last_name = "Adams" last_name: @last_name = "Adams"
email: @email = "joe@example.com" email: @email = "joe@example.com"
user_id: @user_id = "user-id-123" user_id: @user_id = "user-id-123"
} }
@client.get = (param, callback) => callback null, @clientParams[param]
@WebsocketController.updateClientPosition @client, @update @WebsocketController.updateClientPosition @client, @update
@populatedCursorData = @populatedCursorData =
@ -604,14 +590,13 @@ describe 'WebsocketController', ->
describe "with a logged in user who has no last_name set", -> describe "with a logged in user who has no last_name set", ->
beforeEach -> beforeEach ->
@clientParams = { @client.ol_context = {
project_id: @project_id project_id: @project_id
first_name: @first_name = "Douglas" first_name: @first_name = "Douglas"
last_name: undefined last_name: undefined
email: @email = "joe@example.com" email: @email = "joe@example.com"
user_id: @user_id = "user-id-123" user_id: @user_id = "user-id-123"
} }
@client.get = (param, callback) => callback null, @clientParams[param]
@WebsocketController.updateClientPosition @client, @update @WebsocketController.updateClientPosition @client, @update
@populatedCursorData = @populatedCursorData =
@ -644,14 +629,13 @@ describe 'WebsocketController', ->
describe "with a logged in user who has no first_name set", -> describe "with a logged in user who has no first_name set", ->
beforeEach -> beforeEach ->
@clientParams = { @client.ol_context = {
project_id: @project_id project_id: @project_id
first_name: undefined first_name: undefined
last_name: @last_name = "Adams" last_name: @last_name = "Adams"
email: @email = "joe@example.com" email: @email = "joe@example.com"
user_id: @user_id = "user-id-123" user_id: @user_id = "user-id-123"
} }
@client.get = (param, callback) => callback null, @clientParams[param]
@WebsocketController.updateClientPosition @client, @update @WebsocketController.updateClientPosition @client, @update
@populatedCursorData = @populatedCursorData =
@ -683,14 +667,13 @@ describe 'WebsocketController', ->
@metrics.inc.calledWith("editor.update-client-position", 0.1).should.equal true @metrics.inc.calledWith("editor.update-client-position", 0.1).should.equal true
describe "with a logged in user who has no names set", -> describe "with a logged in user who has no names set", ->
beforeEach -> beforeEach ->
@clientParams = { @client.ol_context = {
project_id: @project_id project_id: @project_id
first_name: undefined first_name: undefined
last_name: undefined last_name: undefined
email: @email = "joe@example.com" email: @email = "joe@example.com"
user_id: @user_id = "user-id-123" user_id: @user_id = "user-id-123"
} }
@client.get = (param, callback) => callback null, @clientParams[param]
@WebsocketController.updateClientPosition @client, @update @WebsocketController.updateClientPosition @client, @update
it "should send the update to the project name with no name", -> it "should send the update to the project name with no name", ->
@ -709,10 +692,9 @@ describe 'WebsocketController', ->
describe "with an anonymous user", -> describe "with an anonymous user", ->
beforeEach -> beforeEach ->
@clientParams = { @client.ol_context = {
project_id: @project_id project_id: @project_id
} }
@client.get = (param, callback) => callback null, @clientParams[param]
@WebsocketController.updateClientPosition @client, @update @WebsocketController.updateClientPosition @client, @update
it "should send the update to the project room with no name", -> it "should send the update to the project room with no name", ->
@ -745,8 +727,8 @@ describe 'WebsocketController', ->
describe "applyOtUpdate", -> describe "applyOtUpdate", ->
beforeEach -> beforeEach ->
@update = {op: {p: 12, t: "foo"}} @update = {op: {p: 12, t: "foo"}}
@client.params.user_id = @user_id @client.ol_context.user_id = @user_id
@client.params.project_id = @project_id @client.ol_context.project_id = @project_id
@WebsocketController._assertClientCanApplyUpdate = sinon.stub().yields() @WebsocketController._assertClientCanApplyUpdate = sinon.stub().yields()
@DocumentUpdaterManager.queueChange = sinon.stub().callsArg(3) @DocumentUpdaterManager.queueChange = sinon.stub().callsArg(3)
@ -807,8 +789,8 @@ describe 'WebsocketController', ->
beforeEach (done) -> beforeEach (done) ->
@client.disconnect = sinon.stub() @client.disconnect = sinon.stub()
@client.emit = sinon.stub() @client.emit = sinon.stub()
@client.params.user_id = @user_id @client.ol_context.user_id = @user_id
@client.params.project_id = @project_id @client.ol_context.project_id = @project_id
error = new Error("update is too large") error = new Error("update is too large")
error.updateSize = 7372835 error.updateSize = 7372835
@DocumentUpdaterManager.queueChange = sinon.stub().callsArgWith(3, error) @DocumentUpdaterManager.queueChange = sinon.stub().callsArgWith(3, error)

View file

@ -18,12 +18,6 @@ describe "WebsocketLoadBalancer", ->
"./RoomManager" : @RoomManager = {eventSource: sinon.stub().returns @RoomEvents} "./RoomManager" : @RoomManager = {eventSource: sinon.stub().returns @RoomEvents}
"./ChannelManager": @ChannelManager = {publish: sinon.stub()} "./ChannelManager": @ChannelManager = {publish: sinon.stub()}
"./ConnectedUsersManager": @ConnectedUsersManager = {refreshClient: sinon.stub()} "./ConnectedUsersManager": @ConnectedUsersManager = {refreshClient: sinon.stub()}
"./Utils": @Utils = {
getClientAttributes: sinon.spy(
(client, _attrs, callback) ->
callback(null, {is_restricted_user: !!client.__isRestricted})
)
}
@io = {} @io = {}
@WebsocketLoadBalancer.rclientPubList = [{publish: sinon.stub()}] @WebsocketLoadBalancer.rclientPubList = [{publish: sinon.stub()}]
@WebsocketLoadBalancer.rclientSubList = [{ @WebsocketLoadBalancer.rclientSubList = [{
@ -87,9 +81,9 @@ describe "WebsocketLoadBalancer", ->
beforeEach -> beforeEach ->
@io.sockets = @io.sockets =
clients: sinon.stub().returns([ clients: sinon.stub().returns([
{id: 'client-id-1', emit: @emit1 = sinon.stub()} {id: 'client-id-1', emit: @emit1 = sinon.stub(), ol_context: {}}
{id: 'client-id-2', emit: @emit2 = sinon.stub()} {id: 'client-id-2', emit: @emit2 = sinon.stub(), ol_context: {}}
{id: 'client-id-1', emit: @emit3 = sinon.stub()} # duplicate client {id: 'client-id-1', emit: @emit3 = sinon.stub(), ol_context: {}} # duplicate client
]) ])
data = JSON.stringify data = JSON.stringify
room_id: @room_id room_id: @room_id
@ -109,10 +103,10 @@ describe "WebsocketLoadBalancer", ->
beforeEach -> beforeEach ->
@io.sockets = @io.sockets =
clients: sinon.stub().returns([ clients: sinon.stub().returns([
{id: 'client-id-1', emit: @emit1 = sinon.stub()} {id: 'client-id-1', emit: @emit1 = sinon.stub(), ol_context: {}}
{id: 'client-id-2', emit: @emit2 = sinon.stub()} {id: 'client-id-2', emit: @emit2 = sinon.stub(), ol_context: {}}
{id: 'client-id-1', emit: @emit3 = sinon.stub()} # duplicate client {id: 'client-id-1', emit: @emit3 = sinon.stub(), ol_context: {}} # duplicate client
{id: 'client-id-4', emit: @emit4 = sinon.stub(), __isRestricted: true} {id: 'client-id-4', emit: @emit4 = sinon.stub(), ol_context: {is_restricted_user: true}}
]) ])
data = JSON.stringify data = JSON.stringify
room_id: @room_id room_id: @room_id
@ -133,10 +127,10 @@ describe "WebsocketLoadBalancer", ->
beforeEach -> beforeEach ->
@io.sockets = @io.sockets =
clients: sinon.stub().returns([ clients: sinon.stub().returns([
{id: 'client-id-1', emit: @emit1 = sinon.stub()} {id: 'client-id-1', emit: @emit1 = sinon.stub(), ol_context: {}}
{id: 'client-id-2', emit: @emit2 = sinon.stub()} {id: 'client-id-2', emit: @emit2 = sinon.stub(), ol_context: {}}
{id: 'client-id-1', emit: @emit3 = sinon.stub()} # duplicate client {id: 'client-id-1', emit: @emit3 = sinon.stub(), ol_context: {}} # duplicate client
{id: 'client-id-4', emit: @emit4 = sinon.stub(), __isRestricted: true} {id: 'client-id-4', emit: @emit4 = sinon.stub(), ol_context: {is_restricted_user: true}}
]) ])
data = JSON.stringify data = JSON.stringify
room_id: @room_id room_id: @room_id

View file

@ -4,15 +4,10 @@ idCounter = 0
module.exports = class MockClient module.exports = class MockClient
constructor: () -> constructor: () ->
@attributes = {} @ol_context = {}
@join = sinon.stub() @join = sinon.stub()
@emit = sinon.stub() @emit = sinon.stub()
@disconnect = sinon.stub() @disconnect = sinon.stub()
@id = idCounter++ @id = idCounter++
@publicId = idCounter++ @publicId = idCounter++
set : (key, value, callback) ->
@attributes[key] = value
callback() if callback?
get : (key, callback) ->
callback null, @attributes[key]
disconnect: () -> disconnect: () ->