Skip to content
This repository was archived by the owner on Apr 2, 2024. It is now read-only.

Commit 70e43d4

Browse files
authored
Merge pull request share#323 from share/revert-shallow-copy-op
revert to using a simple shallowCopy for ops
2 parents 9effa65 + f7e4265 commit 70e43d4

File tree

2 files changed

+92
-10
lines changed

2 files changed

+92
-10
lines changed

lib/agent.js

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,7 @@ Agent.prototype._onOp = function(collection, id, op) {
172172
// before calling into projection and middleware code
173173
var agent = this;
174174
process.nextTick(function() {
175-
var copy = shallowCopyOp(op);
176-
if (!copy) {
177-
logger.error('Op emitted from subscription failed to copy', collection, id, op);
178-
return;
179-
}
175+
var copy = shallowCopy(op);
180176
agent.backend.sanitizeOp(agent, collection, id, copy, function(err) {
181177
if (err) {
182178
logger.error('Error sanitizing op emitted from subscription', collection, id, copy, err);
@@ -640,11 +636,12 @@ function createClientOp(request, clientId) {
640636
undefined;
641637
}
642638

643-
function shallowCopyOp(op) {
644-
return (op.op) ? new EditOp(op.src, op.seq, op.v, op.op, op.c, op.d, op.m) :
645-
(op.create) ? new CreateOp(op.src, op.seq, op.v, op.create, op.c, op.d, op.m) :
646-
(op.del) ? new DeleteOp(op.src, op.seq, op.v, op.del, op.c, op.d, op.m) :
647-
undefined;
639+
function shallowCopy(object) {
640+
var out = {};
641+
for (var key in object) {
642+
out[key] = object[key];
643+
}
644+
return out;
648645
}
649646

650647
function CreateOp(src, seq, v, create, c, d, m) {

test/middleware.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,4 +303,89 @@ describe('middleware', function() {
303303
});
304304
});
305305
});
306+
307+
describe('access control', function() {
308+
function setupOpMiddleware(backend) {
309+
backend.use('apply', function(request, next) {
310+
request.priorAccountId = request.snapshot.data && request.snapshot.data.accountId;
311+
next();
312+
});
313+
backend.use('commit', function(request, next) {
314+
var accountId = (request.snapshot.data) ?
315+
// For created documents, get the accountId from the document data
316+
request.snapshot.data.accountId :
317+
// For deleted documents, get the accountId from before
318+
request.priorAccountId;
319+
// Store the accountId for the document on the op for efficient access control
320+
request.op.accountId = accountId;
321+
next();
322+
});
323+
backend.use('op', function(request, next) {
324+
if (request.op.accountId === request.agent.accountId) {
325+
return next();
326+
}
327+
var err = {message: 'op accountId does not match', code: 'ERR_OP_READ_FORBIDDEN'};
328+
return next(err);
329+
});
330+
}
331+
332+
it('is possible to cache add additional top-level fields on ops for access control', function(done) {
333+
setupOpMiddleware(this.backend);
334+
var connection1 = this.backend.connect();
335+
var connection2 = this.backend.connect();
336+
connection2.agent.accountId = 'foo';
337+
338+
// Fetching the snapshot here will cause subsequent fetches to get ops
339+
connection2.get('dogs', 'fido').fetch(function(err) {
340+
if (err) return done(err);
341+
var data = {accountId: 'foo', age: 2};
342+
connection1.get('dogs', 'fido').create(data, function(err) {
343+
if (err) return done(err);
344+
// This will go through the 'op' middleware and should pass
345+
connection2.get('dogs', 'fido').fetch(done);
346+
});
347+
});
348+
});
349+
350+
it('op middleware can reject ops', function(done) {
351+
setupOpMiddleware(this.backend);
352+
var connection1 = this.backend.connect();
353+
var connection2 = this.backend.connect();
354+
connection2.agent.accountId = 'baz';
355+
356+
// Fetching the snapshot here will cause subsequent fetches to get ops
357+
connection2.get('dogs', 'fido').fetch(function(err) {
358+
if (err) return done(err);
359+
var data = {accountId: 'foo', age: 2};
360+
connection1.get('dogs', 'fido').create(data, function(err) {
361+
if (err) return done(err);
362+
// This will go through the 'op' middleware and fail;
363+
connection2.get('dogs', 'fido').fetch(function(err) {
364+
expect(err.code).equal('ERR_OP_READ_FORBIDDEN');
365+
done();
366+
});
367+
});
368+
});
369+
});
370+
371+
it('pubsub subscribe can check top-level fields for access control', function(done) {
372+
setupOpMiddleware(this.backend);
373+
var connection1 = this.backend.connect();
374+
var connection2 = this.backend.connect();
375+
connection2.agent.accountId = 'foo';
376+
377+
// Fetching the snapshot here will cause subsequent fetches to get ops
378+
connection2.get('dogs', 'fido').subscribe(function(err) {
379+
if (err) return done(err);
380+
var data = {accountId: 'foo', age: 2};
381+
connection1.get('dogs', 'fido').create(data, function(err) {
382+
if (err) return done(err);
383+
// The subscribed op will go through the 'op' middleware and should pass
384+
connection2.get('dogs', 'fido').on('create', function() {
385+
done();
386+
});
387+
});
388+
});
389+
});
390+
});
306391
});

0 commit comments

Comments
 (0)