|
1 | 1 | 'use strict'; |
2 | 2 |
|
3 | | -const AttributePool = require('../../../static/js/AttributePool'); |
4 | 3 | const assert = require('assert').strict; |
5 | 4 | const common = require('../common'); |
6 | 5 | const padManager = require('../../../node/db/PadManager'); |
| 6 | +const plugins = require('../../../static/js/pluginfw/plugin_defs'); |
| 7 | +const readOnlyManager = require('../../../node/db/ReadOnlyManager'); |
7 | 8 |
|
8 | 9 | describe(__filename, function () { |
9 | 10 | let agent; |
10 | 11 | let pad; |
11 | 12 | let padId; |
| 13 | + let roPadId; |
12 | 14 | let rev; |
13 | 15 | let socket; |
| 16 | + let roSocket; |
| 17 | + const backups = {}; |
14 | 18 |
|
15 | 19 | before(async function () { |
16 | 20 | agent = await common.init(); |
17 | 21 | }); |
18 | 22 |
|
19 | 23 | beforeEach(async function () { |
| 24 | + backups.hooks = {handleMessageSecurity: plugins.hooks.handleMessageSecurity}; |
| 25 | + plugins.hooks.handleMessageSecurity = []; |
20 | 26 | padId = common.randomString(); |
21 | 27 | assert(!await padManager.doesPadExist(padId)); |
22 | | - pad = await padManager.getPad(padId, ''); |
| 28 | + pad = await padManager.getPad(padId, 'dummy text'); |
| 29 | + await pad.setText('\n'); // Make sure the pad is created. |
23 | 30 | assert.equal(pad.text(), '\n'); |
24 | | - const res = await agent.get(`/p/${padId}`).expect(200); |
| 31 | + let res = await agent.get(`/p/${padId}`).expect(200); |
25 | 32 | socket = await common.connect(res); |
26 | 33 | const {type, data: clientVars} = await common.handshake(socket, padId); |
27 | 34 | assert.equal(type, 'CLIENT_VARS'); |
28 | 35 | rev = clientVars.collab_client_vars.rev; |
| 36 | + |
| 37 | + roPadId = await readOnlyManager.getReadOnlyId(padId); |
| 38 | + res = await agent.get(`/p/${roPadId}`).expect(200); |
| 39 | + roSocket = await common.connect(res); |
| 40 | + await common.handshake(roSocket, roPadId, `t.${common.randomString(8)}`); |
29 | 41 | }); |
30 | 42 |
|
31 | 43 | afterEach(async function () { |
| 44 | + Object.assign(plugins.hooks, backups.hooks); |
32 | 45 | if (socket != null) socket.close(); |
33 | 46 | socket = null; |
| 47 | + if (roSocket != null) roSocket.close(); |
| 48 | + roSocket = null; |
34 | 49 | if (pad != null) await pad.remove(); |
35 | 50 | pad = null; |
36 | 51 | }); |
37 | 52 |
|
38 | 53 | describe('USER_CHANGES', function () { |
39 | 54 | const sendUserChanges = |
40 | | - async (changeset) => await common.sendUserChanges(socket, {baseRev: rev, changeset}); |
41 | | - const assertAccepted = async (wantRev) => { |
| 55 | + async (socket, cs) => await common.sendUserChanges(socket, {baseRev: rev, changeset: cs}); |
| 56 | + const assertAccepted = async (socket, wantRev) => { |
42 | 57 | await common.waitForAcceptCommit(socket, wantRev); |
43 | 58 | rev = wantRev; |
44 | 59 | }; |
45 | | - const assertRejected = async () => { |
| 60 | + const assertRejected = async (socket) => { |
46 | 61 | const msg = await common.waitForSocketEvent(socket, 'message'); |
47 | 62 | assert.deepEqual(msg, {disconnect: 'badChangeset'}); |
48 | 63 | }; |
49 | 64 |
|
50 | 65 | it('changes are applied', async function () { |
51 | 66 | await Promise.all([ |
52 | | - assertAccepted(rev + 1), |
53 | | - sendUserChanges('Z:1>5+5$hello'), |
| 67 | + assertAccepted(socket, rev + 1), |
| 68 | + sendUserChanges(socket, 'Z:1>5+5$hello'), |
54 | 69 | ]); |
55 | 70 | assert.equal(pad.text(), 'hello\n'); |
56 | 71 | }); |
57 | 72 |
|
58 | 73 | it('bad changeset is rejected', async function () { |
59 | 74 | await Promise.all([ |
60 | | - assertRejected(), |
61 | | - sendUserChanges('this is not a valid changeset'), |
| 75 | + assertRejected(socket), |
| 76 | + sendUserChanges(socket, 'this is not a valid changeset'), |
62 | 77 | ]); |
63 | 78 | }); |
64 | 79 |
|
65 | 80 | it('retransmission is accepted, has no effect', async function () { |
66 | 81 | const cs = 'Z:1>5+5$hello'; |
67 | 82 | await Promise.all([ |
68 | | - assertAccepted(rev + 1), |
69 | | - sendUserChanges(cs), |
| 83 | + assertAccepted(socket, rev + 1), |
| 84 | + sendUserChanges(socket, cs), |
70 | 85 | ]); |
71 | 86 | --rev; |
72 | 87 | await Promise.all([ |
73 | | - assertAccepted(rev + 1), |
74 | | - sendUserChanges(cs), |
| 88 | + assertAccepted(socket, rev + 1), |
| 89 | + sendUserChanges(socket, cs), |
75 | 90 | ]); |
76 | 91 | assert.equal(pad.text(), 'hello\n'); |
77 | 92 | }); |
78 | 93 |
|
79 | 94 | it('identity changeset is accepted, has no effect', async function () { |
80 | 95 | await Promise.all([ |
81 | | - assertAccepted(rev + 1), |
82 | | - sendUserChanges('Z:1>5+5$hello'), |
| 96 | + assertAccepted(socket, rev + 1), |
| 97 | + sendUserChanges(socket, 'Z:1>5+5$hello'), |
83 | 98 | ]); |
84 | 99 | await Promise.all([ |
85 | | - assertAccepted(rev), |
86 | | - sendUserChanges('Z:6>0$'), |
| 100 | + assertAccepted(socket, rev), |
| 101 | + sendUserChanges(socket, 'Z:6>0$'), |
87 | 102 | ]); |
88 | 103 | assert.equal(pad.text(), 'hello\n'); |
89 | 104 | }); |
90 | 105 |
|
91 | 106 | it('non-identity changeset with no net change is accepted, has no effect', async function () { |
92 | 107 | await Promise.all([ |
93 | | - assertAccepted(rev + 1), |
94 | | - sendUserChanges('Z:1>5+5$hello'), |
| 108 | + assertAccepted(socket, rev + 1), |
| 109 | + sendUserChanges(socket, 'Z:1>5+5$hello'), |
| 110 | + ]); |
| 111 | + await Promise.all([ |
| 112 | + assertAccepted(socket, rev), |
| 113 | + sendUserChanges(socket, 'Z:6>0-5+5$hello'), |
95 | 114 | ]); |
| 115 | + assert.equal(pad.text(), 'hello\n'); |
| 116 | + }); |
| 117 | + |
| 118 | + it('handleMessageSecurity can grant one-time write access', async function () { |
| 119 | + const cs = 'Z:1>5+5$hello'; |
| 120 | + // First try to send a change and verify that it was dropped. |
| 121 | + await sendUserChanges(roSocket, cs); |
| 122 | + // sendUserChanges() waits for message ack, so if the message was accepted then head should |
| 123 | + // have already incremented by the time we get here. |
| 124 | + assert.equal(pad.head, rev); // Not incremented. |
| 125 | + |
| 126 | + // Now allow the change. |
| 127 | + plugins.hooks.handleMessageSecurity.push({hook_fn: () => 'permitOnce'}); |
96 | 128 | await Promise.all([ |
97 | | - assertAccepted(rev), |
98 | | - sendUserChanges('Z:6>0-5+5$hello'), |
| 129 | + assertAccepted(roSocket, rev + 1), |
| 130 | + sendUserChanges(roSocket, cs), |
99 | 131 | ]); |
100 | 132 | assert.equal(pad.text(), 'hello\n'); |
| 133 | + |
| 134 | + // The next change should be dropped. |
| 135 | + plugins.hooks.handleMessageSecurity = []; |
| 136 | + await sendUserChanges(roSocket, 'Z:6>6=5+6$ world'); |
| 137 | + assert.equal(pad.head, rev); // Not incremented. |
| 138 | + assert.equal(pad.text(), 'hello\n'); |
101 | 139 | }); |
102 | 140 | }); |
103 | 141 | }); |
0 commit comments