Skip to content

Commit

Permalink
insert and update methods take a skippable options parameter, which…
Browse files Browse the repository at this point in the history
… may specify `permissions: false`. The correct doc type edit permission is checked. Dead config code removal in permissions module. If permissions are being skipped, do not add a permission for the current user as creator. beforeCreate/afterCreate are now beforeInsert/afterInsert to match the method names. Piece managers call their override point methods from their insert and update methods, not just from the routes. Eliminated iffy findIfContextual method of pieces. Unit tests passing, new forms tutorial tests new permissions: false functionality successfully. Will test more this afternoon before merging. See corresponding PR for apostrophe-events.
  • Loading branch information
Tom Boutell committed Sep 2, 2016
1 parent 4af711c commit ac1476d
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 112 deletions.
136 changes: 100 additions & 36 deletions lib/modules/apostrophe-docs/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,30 @@ module.exports = function(self, options) {
return self.apos.create('apostrophe-cursor', { apos: self.apos, req: req, criteria: criteria, projection: projection });
};

// **Most often you will insert or update docs via the
// insert and update methods of the appropriate doc manager.**
// This method is for implementation use in those objects,
// and for times when you wish to explicitly bypass most type-specific
// callbacks such as the `beforeInsert` method of the
// doc manager.
//
// Insert the given document. If the slug is not
// unique it is made unique. docBeforeInsert,
// docBeforeSave, docAfterInsert
// and docAfterSave are called on all modules.
// and docAfterSave are called on all modules that have them.
// These have a performance impact so they should only be used
// for critical matters and cross-cutting concerns such as versioning
// and security.
//
// On success the callback is invoked with
// (null, doc).
//
// If the slug property is not set, the title
// property is converted to a slug. If neither
// property is set, an error occurs.
//
// The `edit-doc` permission is checked for the
// general case. `beforeInsertDoc` methods can
// be used to enforce other restrictions.
// The `edit-type-name` permission is checked based on
// doc.type.
//
// If a unique key error occurs,
// apos.*.docFixUniqueError is called with the
Expand All @@ -103,26 +113,35 @@ module.exports = function(self, options) {
// not possible to know which property was
// responsible. This method takes care of
// the slug property directly.
//
// The `options` object may be omitted completely.
//
// If `options.permissions` is set explicitly to
// `false`, permissions checks are bypassed.

self.insert = function(req, doc, callback) {
self.insert = function(req, doc, options, callback) {
if (arguments.length === 3) {
callback = arguments[2];
options = {};
}
return async.series({
beforeInsert: function(callback) {
return self.apos.callAll('docBeforeInsert', req, doc, callback);
return self.apos.callAll('docBeforeInsert', req, doc, options, callback);
},
beforeSave: function(callback) {
return self.apos.callAll('docBeforeSave', req, doc, callback);
return self.apos.callAll('docBeforeSave', req, doc, options, callback);
},
denormalizePermissions: function(callback) {
return self.denormalizePermissions(req, doc, callback);
return self.denormalizePermissions(req, doc, options, callback);
},
insert: function(callback) {
return self.insertBody(req, doc, callback);
return self.insertBody(req, doc, options, callback);
},
afterInsert: function(callback) {
return self.apos.callAll('docAfterInsert', req, doc, callback);
return self.apos.callAll('docAfterInsert', req, doc, options, callback);
},
afterSave: function(callback) {
return self.apos.callAll('docAfterSave', req, doc, callback);
return self.apos.callAll('docAfterSave', req, doc, options, callback);
},
load: function(callback) {
// since we deliver the new doc to the callback,
Expand All @@ -137,6 +156,18 @@ module.exports = function(self, options) {
});
};

// **Most often you will insert or update docs via the
// insert and update methods of the appropriate doc manager.**
// This method is for implementation use in those objects,
// and for times when you wish to explicitly bypass most type-specific
// callbacks such as the `beforeUpdate` method of the
// doc manager.
//
// Update the given document. If the slug is not
// unique it is made unique. docBeforeInsert,
// docBeforeSave, docAfterInsert
// and docAfterSave are called on all modules that have them.
//
// Update a single document.
//
// The second argument must be the document itself.
Expand All @@ -147,7 +178,9 @@ module.exports = function(self, options) {
//
// docBeforeSave, docBeforeUpdate,
// docAfterSave and docAfterUpdate are invoked on
// all modules.
// all modules that have them. These have a performance
// impact, so they should be used only to implement
// cross-cutting concerns like versioning and address security matters.
//
// On success the callback is invoked with
// (null, doc).
Expand All @@ -166,26 +199,35 @@ module.exports = function(self, options) {
// not possible to know which property was
// responsible. This method takes care of
// the slug property directly.
//
// The `options` object may be omitted completely.
//
// If `options.permissions` is set explicitly to
// `false`, permissions checks are bypassed.

self.update = function(req, doc, callback) {
self.update = function(req, doc, options, callback) {
if (arguments.length === 3) {
callback = arguments[2];
options = {};
}
return async.series({
beforeUpdate: function(callback) {
return self.apos.callAll('docBeforeUpdate', req, doc, callback);
return self.apos.callAll('docBeforeUpdate', req, doc, options, callback);
},
beforeSave: function(callback) {
return self.apos.callAll('docBeforeSave', req, doc, callback);
return self.apos.callAll('docBeforeSave', req, doc, options, callback);
},
denormalizePermissions: function(callback) {
return self.denormalizePermissions(req, doc, callback);
return self.denormalizePermissions(req, doc, options, callback);
},
update: function(callback) {
return self.updateBody(req, doc, callback);
return self.updateBody(req, doc, options, callback);
},
afterUpdate: function(callback) {
return self.apos.callAll('docAfterUpdate', req, doc, callback);
return self.apos.callAll('docAfterUpdate', req, doc, options, callback);
},
afterSave: function(callback) {
return self.apos.callAll('docAfterSave', req, doc, callback);
return self.apos.callAll('docAfterSave', req, doc, options, callback);
},
}, function(err) {
return callback(err, (!err) && doc);
Expand All @@ -198,8 +240,11 @@ module.exports = function(self, options) {
// or a group id. This method performs that copying. It also invokes
// the docAfterDenormalizePermissions method of every module that has one,
// which allows the pages module to piggyback and add `applyToSubpages` behavior.
//
// The `options` object is for future extension and is passed on
// to this method by `insert` and `update`.

self.denormalizePermissions = function(req, doc, callback) {
self.denormalizePermissions = function(req, doc, options, callback) {
var admin = req.user && req.user._permissions.admin;
var fields = {
viewGroupsIds: 'view',
Expand All @@ -216,7 +261,7 @@ module.exports = function(self, options) {
);
});
doc.docPermissions = docPermissions;
return self.apos.callAll('docAfterDenormalizePermissions', req, doc, callback);
return self.apos.callAll('docAfterDenormalizePermissions', req, doc, options, callback);
};

// Trash a single document. The second
Expand Down Expand Up @@ -452,17 +497,22 @@ module.exports = function(self, options) {

// Invoked before any doc is inserted. Checks
// that the user has general permission to
// create docs, generates an _id if needed,
// create docs of that type, generates an _id if needed,
// and sets createdAt to the current Date.
// Note that methods of this name are invoked
// on ALL modules that have them, starting with
// this one. Although this method takes no
// callback, other implementations MAY
// take a callback and are invoked in series.
//
// If `options.permissions` is explicitly `false`,
// permissions checks are not performed.

self.docBeforeInsert = function(req, doc) {
if (!self.apos.permissions.can(req, 'edit-doc')) {
throw new Error('forbidden');
self.docBeforeInsert = function(req, doc, options) {
if (!(options.permissions === false)) {
if (!self.apos.permissions.can(req, 'edit-' + doc.type)) {
throw new Error('forbidden');
}
}
if (!doc._id) {
doc._id = self.apos.utils.generateId();
Expand All @@ -484,7 +534,7 @@ module.exports = function(self, options) {
// callback, other implementations MAY
// take a callback and are invoked in series.

self.docBeforeSave = function(req, doc) {
self.docBeforeSave = function(req, doc, options) {
if (!doc.slug) {
if (doc.title) {
doc.slug = self.apos.utils.slugify(doc.title);
Expand Down Expand Up @@ -528,29 +578,43 @@ module.exports = function(self, options) {
// this one. Although this method takes no
// callback, other implementations MAY
// take a callback and are invoked in series.
//
// If `options.permissions` is explicitly `false`,
// permissions checks are not performed.

self.docBeforeUpdate = function(req, doc) {
if (!self.apos.permissions.can(req, 'edit-doc', doc)) {
throw new Error('forbidden');
self.docBeforeUpdate = function(req, doc, options) {
if (options.permissions !== false) {
if (!self.apos.permissions.can(req, 'edit-' + doc.type, doc)) {
throw new Error('forbidden');
}
}
};

// Do not call this yourself, it is called
// by .update. You may override this method
// to change the implementation.
// by .update(). You will usually want to call the
// update method of the appropriate doc type manager instead:
//
// self.apos.docs.getManager(doc.type).update(...)
//
// You may override this method to change the implementation.

self.updateBody = function(req, doc, callback) {
self.updateBody = function(req, doc, options, callback) {
return self.retryUntilUnique(req, doc, function(callback) {
return self.db.update({ _id: doc._id }, self.apos.utils.clonePermanent(doc), callback);
}, callback);
};

// Insert the given document. You
// should call .insert(), not this method. However
// you can override this method to alter the
// Insert the given document. Called by `.insert()`. You will usually want to
// call the update method of the appropriate doc type manager instead:
//
//```javascript
//self.apos.docs.getManager(doc.type).update(...)
//```
//
// However you can override this method to alter the
// implementation.

self.insertBody = function(req, doc, callback) {
self.insertBody = function(req, doc, options, callback) {
return self.retryUntilUnique(req, doc, function(callback) {
return self.db.insert(self.apos.utils.clonePermanent(doc), callback);
}, callback);
Expand Down
2 changes: 1 addition & 1 deletion lib/modules/apostrophe-pages/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ module.exports = function(self, options) {
// operations, so we first make sure it's a page. We also make sure it's
// not a new page (no kids to propagate anything to).

self.docAfterDenormalizePermissions = function(req, page, callback) {
self.docAfterDenormalizePermissions = function(req, page, options, callback) {

if (!self.isPage(page)) {
return setImmediate(callback);
Expand Down
11 changes: 2 additions & 9 deletions lib/modules/apostrophe-permissions/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,8 @@ module.exports = function(self, options) {

self.publicPermissions = {};

self.types = options.types || {
doc: {
strategy: 'doc'
},
file: {
strategy: 'owner'
}
};

// Currently every type uses the doc strategy, which offers nuanced permissions
self.types = {};
self.defaultStrategy = options.defaultStrategy || 'doc';

self._check = function(req, action, event, _true, _false, object, then) {
Expand Down
Loading

0 comments on commit ac1476d

Please sign in to comment.