Skip to content

Commit 0b03b8e

Browse files
committed
refactor(realtime): ifMayEdit
Signed-off-by: BoHong Li <[email protected]>
1 parent e773182 commit 0b03b8e

File tree

3 files changed

+162
-17
lines changed

3 files changed

+162
-17
lines changed

lib/realtime.js

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
const cookie = require('cookie')
55
const cookieParser = require('cookie-parser')
66
const url = require('url')
7-
const async = require('async')
87
const randomcolor = require('randomcolor')
98
const Chance = require('chance')
109
const chance = new Chance()
@@ -44,14 +43,17 @@ const updateDirtyNoteJob = new UpdateDirtyNoteJob(realtime)
4443
const cleanDanglingUserJob = new CleanDanglingUserJob(realtime)
4544
const saveRevisionJob = new SaveRevisionJob(realtime)
4645

46+
// TODO: test it
4747
function onAuthorizeSuccess (data, accept) {
4848
accept()
4949
}
5050

51+
// TODO: test it
5152
function onAuthorizeFail (data, message, error, accept) {
5253
accept() // accept whether authorize or not to allow anonymous usage
5354
}
5455

56+
// TODO: test it
5557
// secure the origin by the cookie
5658
function secure (socket, next) {
5759
try {
@@ -78,6 +80,7 @@ function secure (socket, next) {
7880
}
7981

8082
// TODO: only use in `updateDirtyNote`
83+
// TODO: test it
8184
function emitCheck (note) {
8285
var out = {
8386
title: note.title,
@@ -202,6 +205,7 @@ async function _updateNoteAsync (note) {
202205
return noteModel
203206
}
204207

208+
// TODO: test it
205209
function getStatus (callback) {
206210
models.Note.count().then(function (notecount) {
207211
var distinctaddresses = []
@@ -257,6 +261,7 @@ function getStatus (callback) {
257261
})
258262
}
259263

264+
// TODO: test it
260265
function isReady () {
261266
return realtime.io &&
262267
Object.keys(notes).length === 0 && Object.keys(users).length === 0 &&
@@ -322,6 +327,7 @@ function parseNoteIdFromSocket (socket, callback) {
322327
})
323328
}
324329

330+
// TODO: test it
325331
function emitOnlineUsers (socket) {
326332
var noteId = socket.noteId
327333
if (!noteId || !notes[noteId]) return
@@ -338,6 +344,7 @@ function emitOnlineUsers (socket) {
338344
realtime.io.to(noteId).emit('online users', out)
339345
}
340346

347+
// TODO: test it
341348
function emitUserStatus (socket) {
342349
var noteId = socket.noteId
343350
var user = users[socket.id]
@@ -346,6 +353,7 @@ function emitUserStatus (socket) {
346353
socket.broadcast.to(noteId).emit('user status', out)
347354
}
348355

356+
// TODO: test it
349357
function emitRefresh (socket) {
350358
var noteId = socket.noteId
351359
if (!noteId || !notes[noteId]) return
@@ -366,6 +374,7 @@ function emitRefresh (socket) {
366374
socket.emit('refresh', out)
367375
}
368376

377+
// TODO: test it
369378
function isDuplicatedInSocketQueue (queue, socket) {
370379
for (var i = 0; i < queue.length; i++) {
371380
if (queue[i] && queue[i].id === socket.id) {
@@ -375,6 +384,7 @@ function isDuplicatedInSocketQueue (queue, socket) {
375384
return false
376385
}
377386

387+
// TODO: test it
378388
function clearSocketQueue (queue, socket) {
379389
for (var i = 0; i < queue.length; i++) {
380390
if (!queue[i] || queue[i].id === socket.id) {
@@ -384,6 +394,7 @@ function clearSocketQueue (queue, socket) {
384394
}
385395
}
386396

397+
// TODO: test it
387398
function connectNextSocket () {
388399
setTimeout(function () {
389400
isConnectionBusy = false
@@ -393,6 +404,7 @@ function connectNextSocket () {
393404
}, 1)
394405
}
395406

407+
// TODO: test it
396408
function interruptConnection (socket, noteId, socketId) {
397409
if (notes[noteId]) delete notes[noteId]
398410
if (users[socketId]) delete users[socketId]
@@ -425,6 +437,7 @@ function checkViewPermission (req, note) {
425437
var isConnectionBusy = false
426438
var connectionSocketQueue = []
427439

440+
// TODO: test it
428441
function finishConnection (socket, noteId, socketId) {
429442
// if no valid info provided will drop the client
430443
if (!socket || !notes[noteId] || !users[socketId]) {
@@ -469,6 +482,7 @@ function finishConnection (socket, noteId, socketId) {
469482
}
470483
}
471484

485+
// TODO: test it
472486
function startConnection (socket) {
473487
if (isConnectionBusy) return
474488
isConnectionBusy = true
@@ -556,6 +570,7 @@ function startConnection (socket) {
556570
}
557571
}
558572

573+
// TODO: test it
559574
function failConnection (code, err, socket) {
560575
logger.error(err)
561576
// clear error socket in queue
@@ -624,6 +639,7 @@ function buildUserOutData (user) {
624639
return out
625640
}
626641

642+
// TODO: test it
627643
function updateUserData (socket, user) {
628644
// retrieve user data from passport
629645
if (socket.request.user && socket.request.user.logged_in) {
@@ -639,31 +655,26 @@ function updateUserData (socket, user) {
639655
}
640656
}
641657

642-
function ifMayEdit (socket, callback) {
643-
var noteId = socket.noteId
644-
if (!noteId || !notes[noteId]) return
645-
var note = notes[noteId]
646-
var mayEdit = true
647-
switch (note.permission) {
658+
function canEditNote(notePermission, noteOwnerId, currentUserId) {
659+
switch (notePermission) {
648660
case 'freely':
649-
// not blocking anyone
650-
break
661+
return true
651662
case 'editable':
652663
case 'limited':
653664
// only login user can change
654-
if (!socket.request.user || !socket.request.user.logged_in) {
655-
mayEdit = false
656-
}
657-
break
665+
return !!currentUserId
658666
case 'locked':
659667
case 'private':
660668
case 'protected':
661669
// only owner can change
662-
if (!note.owner || note.owner !== socket.request.user.id) {
663-
mayEdit = false
664-
}
665-
break
670+
return noteOwnerId === currentUserId
666671
}
672+
}
673+
674+
function ifMayEdit (socket, callback) {
675+
const note = getNoteFromNotePool(socket.noteId)
676+
if (!note) return
677+
const mayEdit = canEditNote(note.permission, note.owner, socket.request.user.id)
667678
// if user may edit and this is a text operation
668679
if (socket.origin === 'operation' && mayEdit) {
669680
// save for the last change user id
@@ -676,6 +687,7 @@ function ifMayEdit (socket, callback) {
676687
return callback(mayEdit)
677688
}
678689

690+
// TODO: test it
679691
function operationCallback (socket, operation) {
680692
var noteId = socket.noteId
681693
if (!noteId || !notes[noteId]) return
@@ -718,6 +730,7 @@ function operationCallback (socket, operation) {
718730
})
719731
}
720732

733+
// TODO: test it
721734
function updateHistory (userId, note, time) {
722735
var noteId = note.alias ? note.alias : models.Note.encodeNoteId(note.id)
723736
if (note.server) history.updateHistory(userId, noteId, note.server.document, time)
@@ -739,6 +752,7 @@ function getNoteFromNotePool (noteId) {
739752
return notes[noteId]
740753
}
741754

755+
// TODO: test it
742756
function connection (socket) {
743757
if (realtime.maintenance) return
744758
exports.parseNoteIdFromSocket(socket, function (err, noteId) {
@@ -798,6 +812,7 @@ function connection (socket) {
798812
socketClient.registerEventHandler()
799813
}
800814

815+
// TODO: test it
801816
function terminate () {
802817
disconnectProcessQueue.stop()
803818
updateDirtyNoteJob.stop()
@@ -825,6 +840,7 @@ exports.queueForDisconnect = queueForDisconnect
825840
exports.terminate = terminate
826841
exports.getUserPool = getUserPool
827842
exports.updateHistory = updateHistory
843+
exports.ifMayEdit = ifMayEdit
828844
exports.disconnectProcessQueue = disconnectProcessQueue
829845
exports.notes = notes
830846
exports.users = users

test/realtime/ifMayEdit.test.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/* eslint-env node, mocha */
2+
'use strict'
3+
4+
const assert = require('assert')
5+
const mock = require('mock-require')
6+
const sinon = require('sinon')
7+
8+
const { createFakeLogger } = require('../testDoubles/loggerFake')
9+
const realtimeJobStub = require('../testDoubles/realtimeJobStub')
10+
const { removeLibModuleCache, makeMockSocket } = require('./utils')
11+
12+
describe('realtime#ifMayEdit', function () {
13+
let modelsStub
14+
beforeEach(() => {
15+
removeLibModuleCache()
16+
mock('../../lib/config', {})
17+
mock('../../lib/logger', createFakeLogger())
18+
mock('../../lib/models', modelsStub)
19+
mock('../../lib/realtimeUpdateDirtyNoteJob', realtimeJobStub)
20+
mock('../../lib/realtimeCleanDanglingUserJob', realtimeJobStub)
21+
mock('../../lib/realtimeSaveRevisionJob', realtimeJobStub)
22+
})
23+
24+
afterEach(() => {
25+
mock.stopAll()
26+
sinon.restore()
27+
})
28+
29+
const Role = {
30+
Guest: 'guest',
31+
LoggedIn: 'LoggedIn',
32+
Owner: 'Owner'
33+
}
34+
35+
const Permission = {
36+
Freely: 'freely',
37+
Editable: 'editable',
38+
Limited: 'limited',
39+
Locked: 'locked',
40+
Protected: 'protected',
41+
Private: 'private'
42+
}
43+
44+
const testcases = [
45+
{ role: Role.Guest, permission: Permission.Freely, canEdit: true },
46+
{ role: Role.LoggedIn, permission: Permission.Freely, canEdit: true },
47+
{ role: Role.Owner, permission: Permission.Freely, canEdit: true },
48+
{ role: Role.Guest, permission: Permission.Editable, canEdit: false },
49+
{ role: Role.LoggedIn, permission: Permission.Editable, canEdit: true },
50+
{ role: Role.Owner, permission: Permission.Editable, canEdit: true },
51+
{ role: Role.Guest, permission: Permission.Limited, canEdit: false },
52+
{ role: Role.LoggedIn, permission: Permission.Limited, canEdit: true },
53+
{ role: Role.Owner, permission: Permission.Limited, canEdit: true },
54+
{ role: Role.Guest, permission: Permission.Locked, canEdit: false },
55+
{ role: Role.LoggedIn, permission: Permission.Locked, canEdit: false },
56+
{ role: Role.Owner, permission: Permission.Locked, canEdit: true },
57+
{ role: Role.Guest, permission: Permission.Protected, canEdit: false},
58+
{ role: Role.LoggedIn, permission: Permission.Protected, canEdit: false },
59+
{ role: Role.Owner, permission: Permission.Protected, canEdit: true },
60+
{ role: Role.Guest, permission: Permission.Private, canEdit: false },
61+
{ role: Role.LoggedIn, permission: Permission.Private, canEdit: false },
62+
{ role: Role.Owner, permission: Permission.Private, canEdit: true }
63+
]
64+
65+
const noteOwnerId = 'owner'
66+
const loggedInUserId = 'user1'
67+
const noteId = 'noteId'
68+
69+
testcases.forEach((tc) => {
70+
it(`${tc.role} ${tc.canEdit ? 'can' : 'can\'t'} edit note with permission ${tc.permission}`, function () {
71+
const client = makeMockSocket()
72+
const note = {
73+
permission: tc.permission,
74+
owner: noteOwnerId
75+
}
76+
if (tc.role === Role.LoggedIn) {
77+
client.request.user.logged_in = true
78+
client.request.user.id = loggedInUserId
79+
} else if (tc.role === Role.Owner) {
80+
client.request.user.logged_in = true
81+
client.request.user.id = noteOwnerId
82+
}
83+
client.noteId = noteId
84+
const realtime = require('../../lib/realtime')
85+
realtime.getNotePool()[noteId] = note
86+
const callback = sinon.stub()
87+
realtime.ifMayEdit(client, callback)
88+
assert(callback.calledOnce)
89+
assert(callback.lastCall.args[0] === tc.canEdit)
90+
})
91+
})
92+
93+
it('should set lsatchangeuser to null if guest edit operation', function () {
94+
const note = {
95+
permission: Permission.Freely
96+
}
97+
const client = makeMockSocket()
98+
client.noteId = noteId
99+
const callback = sinon.stub()
100+
client.origin = 'operation'
101+
const realtime = require('../../lib/realtime')
102+
realtime.getNotePool()[noteId] = note
103+
realtime.ifMayEdit(client, callback)
104+
assert(callback.calledOnce)
105+
assert(callback.lastCall.args[0])
106+
assert(note.lastchangeuser === null)
107+
})
108+
109+
it('should set lastchangeuser to logged_in user id if user edit', function () {
110+
const note = {
111+
permission: Permission.Freely
112+
}
113+
const client = makeMockSocket()
114+
client.noteId = noteId
115+
client.request.user.logged_in = true
116+
client.request.user.id = loggedInUserId
117+
const callback = sinon.stub()
118+
client.origin = 'operation'
119+
const realtime = require('../../lib/realtime')
120+
realtime.getNotePool()[noteId] = note
121+
realtime.ifMayEdit(client, callback)
122+
assert(callback.calledOnce)
123+
assert(callback.lastCall.args[0])
124+
assert(note.lastchangeuser === loggedInUserId)
125+
})
126+
})

test/realtime/utils.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ function makeMockSocket (headers, query) {
77
const broadCastChannelCache = {}
88
return {
99
id: Math.round(Math.random() * 10000),
10+
request: {
11+
user: {}
12+
},
1013
handshake: {
1114
headers: Object.assign({}, headers),
1215
query: Object.assign({}, query)

0 commit comments

Comments
 (0)