Skip to content

Commit

Permalink
feat: make create and update respect ACL fields object
Browse files Browse the repository at this point in the history
  • Loading branch information
eduardoboucas committed Dec 7, 2018
1 parent d7624f8 commit 3547fbb
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 62 deletions.
3 changes: 2 additions & 1 deletion dadi/lib/model/acl/access.js
Expand Up @@ -152,7 +152,8 @@ Access.prototype.get = function ({clientId = null, accessType = null} = {}, reso
}

return this.model.get({
query
query,
rawOutput: true
}).then(({results}) => {
if (results.length === 0) {
return {}
Expand Down
9 changes: 7 additions & 2 deletions dadi/lib/model/acl/index.js
Expand Up @@ -5,6 +5,9 @@ const Connection = require('./../connection')
const Model = require('./../index')
const role = require('./role')

const ERROR_FORBIDDEN = 'FORBIDDEN'
const ERROR_UNAUTHORISED = 'UNAUTHORISED'

const ACL = function () {
this.resources = {}

Expand Down Expand Up @@ -86,10 +89,10 @@ ACL.prototype.createError = function (client) {
// authenticated, just not authorised. That is a 403. In any other case,
// the request is unauthorised, so a 401 is returned.
if (client && client.clientId && !client.error) {
return new Error('FORBIDDEN')
return new Error(ERROR_FORBIDDEN)
}

return new Error('UNAUTHORISED')
return new Error(ERROR_UNAUTHORISED)
}

ACL.prototype.getResources = function () {
Expand All @@ -108,6 +111,8 @@ ACL.prototype.registerResource = function (name, description = null) {

module.exports = new ACL()
module.exports.ACL = ACL
module.exports.ERROR_FORBIDDEN = ERROR_FORBIDDEN
module.exports.ERROR_UNAUTHORISED = ERROR_UNAUTHORISED
module.exports.access = access
module.exports.client = client
module.exports.role = role
53 changes: 32 additions & 21 deletions dadi/lib/model/collections/create.js
Expand Up @@ -58,12 +58,26 @@ function create ({
return document
})

let {hooks} = this.settings

// If an ACL check is performed, this variable will contain the resulting
// access matrix.
let aclAccess

return this.validateAccess({
client,
documents,
type: 'create'
}).then(({schema}) => {
}).then(({access, documents: newDocuments, fields, schema}) => {
if (!validate) return

// Storing the access matrix in a variable that is global to the method.
aclAccess = access

// This is now the filtered documents object, containing only the fields
// which the client has access to.
documents = newDocuments

return this.validator.validateDocuments({
documents,
schema
Expand Down Expand Up @@ -113,16 +127,16 @@ function create ({
return transformQueue
}).then(documents => {
// Run any `beforeCreate` hooks.
if (this.settings.hooks && this.settings.hooks.beforeCreate) {
if (hooks && hooks.beforeCreate) {
return new Promise((resolve, reject) => {
let processedDocuments = 0

documents.forEach((doc, docIndex) => {
async.reduce(this.settings.hooks.beforeCreate, doc, (current, hookConfig, callback) => {
async.reduce(hooks.beforeCreate, doc, (current, hookConfig, callback) => {
let hook = new Hook(hookConfig, 'beforeCreate')

Promise.resolve(hook.apply(current, this.schema, this.name, req))
.then((newDoc) => {
.then(newDoc => {
callback((newDoc === null) ? {} : null, newDoc)
})
.catch(err => {
Expand Down Expand Up @@ -151,34 +165,31 @@ function create ({
schema: this.schema,
settings: this.settings
}).then(results => {
let returnData = {
results
}

// Asynchronous search index.
this.searchHandler.index(returnData.results)
this.searchHandler.index(results)

// Run any `afterCreate` hooks.
if (this.settings.hooks && (typeof this.settings.hooks.afterCreate === 'object')) {
returnData.results.forEach(document => {
this.settings.hooks.afterCreate.forEach((hookConfig, index) => {
let hook = new Hook(this.settings.hooks.afterCreate[index], 'afterCreate')
if (hooks && (typeof hooks.afterCreate === 'object')) {
results.forEach(document => {
hooks.afterCreate.forEach((hookConfig, index) => {
let hook = new Hook(hooks.afterCreate[index], 'afterCreate')

return hook.apply(document, this.schema, this.name)
})
})
}

// Prepare result set for output.
if (!rawOutput) {
return this.formatForOutput(
returnData.results,
{
composeOverride: compose
}).then(results => ({results}))
// If `rawOutput` is truthy, we don't need to worry about formatting
// the result set for output. We return it as is.
if (rawOutput) {
return {results}
}

return returnData
return this.formatForOutput(results, {
access: aclAccess && aclAccess.read,
client,
composeOverride: compose
}).then(results => ({results}))
})
})
}
Expand Down
34 changes: 20 additions & 14 deletions dadi/lib/model/collections/get.js
Expand Up @@ -24,18 +24,20 @@ const logger = require('@dadi/logger')
* Finds documents in the database, running any configured hooks
* and formatting the result set for final output.
*
* @param {Object} client - client to check permissions for
* @param {String} language - ISO code for the language to translate documents to
* @param {Object} query - query to match documents against
* @param {Object} options
* @param {Object} req - request object to pass to hooks
* @param {Object} client - client to check permissions for
* @param {String} language - ISO code for the language to translate documents to
* @param {Object} query - query to match documents against
* @param {Object} options
* @param {Boolean} rawOutput - whether to bypass formatting routine
* @param {Object} req - request object to pass to hooks
* @return {Promise<ResultSet>}
*/
function get ({
client,
language,
query = {},
options = {},
rawOutput = false,
req
}) {
// Is this a RESTful query by ID?
Expand Down Expand Up @@ -112,15 +114,19 @@ function get ({

return response
}).then(({metadata, results}) => {
return this.formatForOutput(
results,
{
client,
composeOverride: options.compose,
language,
urlFields: options.fields
}
).then(results => {
let formatter = rawOutput
? Promise.resolve(results)
: this.formatForOutput(
results,
{
client,
composeOverride: options.compose,
language,
urlFields: options.fields
}
)

return formatter.then(results => {
return {results, metadata}
})
})
Expand Down
36 changes: 23 additions & 13 deletions dadi/lib/model/collections/update.js
Expand Up @@ -77,12 +77,26 @@ function update ({
// Get a reference to the documents that will be updated.
let updatedDocuments = []

// Removing internal API properties from the update object.
if (removeInternalProperties) {
update = this.removeInternalProperties(update)
}

let {hooks} = this.settings

// If an ACL check is performed, this variable will contain the resulting
// access matrix.
let aclAccess

return this.validateAccess({
client,
documents: update,
query,
type: 'update'
}).then(({query: aclQuery, schema}) => {
}).then(({access, documents: newUpdate, query: aclQuery, schema}) => {
aclAccess = access
query = aclQuery
update = newUpdate

// If merging the request query with ACL data resulted in
// an impossible query, we can simply return an empty result
Expand All @@ -92,11 +106,6 @@ function update ({
return Promise.reject(query)
}

// Removing internal API properties from the update object.
if (removeInternalProperties) {
update = this.removeInternalProperties(update)
}

if (!validate) return

// Validating the query.
Expand Down Expand Up @@ -160,9 +169,9 @@ function update ({
update = transformedUpdate

// Run any `beforeUpdate` hooks.
if (this.settings.hooks && this.settings.hooks.beforeUpdate) {
if (hooks && hooks.beforeUpdate) {
return new Promise((resolve, reject) => {
async.reduce(this.settings.hooks.beforeUpdate, update, (current, hookConfig, callback) => {
async.reduce(hooks.beforeUpdate, update, (current, hookConfig, callback) => {
let hook = new Hook(hookConfig, 'beforeUpdate')

Promise.resolve(hook.apply(current, updatedDocuments, this.schema, this.name, req))
Expand Down Expand Up @@ -220,20 +229,20 @@ function update ({
}

return this.find({
query: updatedDocumentsQuery,
options: {
compose: true
}
},
query: updatedDocumentsQuery
})
}).then(data => {
if (data.results.length === 0) {
return data
}

// Run any `afterUpdate` hooks.
if (this.settings.hooks && (typeof this.settings.hooks.afterUpdate === 'object')) {
this.settings.hooks.afterUpdate.forEach((hookConfig, index) => {
let hook = new Hook(this.settings.hooks.afterUpdate[index], 'afterUpdate')
if (hooks && (typeof hooks.afterUpdate === 'object')) {
hooks.afterUpdate.forEach((hookConfig, index) => {
let hook = new Hook(hooks.afterUpdate[index], 'afterUpdate')

return hook.apply(data.results, this.schema, this.name)
})
Expand All @@ -245,6 +254,7 @@ function update ({
// Format result set for output.
if (!rawOutput) {
return this.formatForOutput(data.results, {
access: aclAccess && aclAccess.read,
client,
composeOverride: compose
}).then(results => {
Expand Down

0 comments on commit 3547fbb

Please sign in to comment.