Skip to content

Commit 4852ae6

Browse files
committed
Merge branch 'release-4.4.4'
2 parents 9842676 + 4a6ccb5 commit 4852ae6

16 files changed

Lines changed: 2574 additions & 399 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/)
55
and this project adheres to [Semantic Versioning](http://semver.org/).
66

7+
## [4.4.4] (2018-12-20)
8+
9+
### Changed
10+
11+
- [#524](https://github.com/dadi/api/pull/524): upgrade `@dadi/status` package
12+
13+
### Fixed
14+
15+
- [#521](https://github.com/dadi/api/issues/521): PUT request should respect `fields` parameter in ACL
16+
- [#522](https://github.com/dadi/api/issues/522): resources API should only list resources which the client has access to
17+
- [#525](https://github.com/dadi/api/issues/525): required fields should not accept empty strings
18+
719
## [4.4.3] (2018-11-29)
820

921
### Fixed

dadi/lib/controller/resources.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,26 @@ Resources.prototype.get = function (req, res, next) {
1414
)
1515
}
1616

17-
let resources = acl.getResources()
17+
let clientIsAdmin = acl.client.isAdmin(req.dadiApiClient)
1818

19-
return help.sendBackJSON(200, res, next)(null, {
20-
results: Object.keys(resources).map(resource => {
21-
return Object.assign({name: resource}, resources[resource])
19+
return acl.access.get(req.dadiApiClient).then(access => {
20+
return Object.keys(access).filter(resource => {
21+
return Object.keys(access[resource]).some(accessType => {
22+
return Boolean(access[resource][accessType])
23+
})
24+
})
25+
}).then(allowedResources => {
26+
let resources = acl.getResources()
27+
let results = Object.keys(resources)
28+
.filter(resource => {
29+
return clientIsAdmin || allowedResources.includes(resource)
30+
})
31+
.map(resource => {
32+
return Object.assign({name: resource}, resources[resource])
33+
})
34+
35+
return help.sendBackJSON(200, res, next)(null, {
36+
results
2237
})
2338
})
2439
}

dadi/lib/model/acl/access.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ Access.prototype.get = function ({clientId = null, accessType = null} = {}, reso
152152
}
153153

154154
return this.model.get({
155-
query
155+
query,
156+
rawOutput: true
156157
}).then(({results}) => {
157158
if (results.length === 0) {
158159
return {}

dadi/lib/model/acl/index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ const Connection = require('./../connection')
55
const Model = require('./../index')
66
const role = require('./role')
77

8+
const ERROR_FORBIDDEN = 'FORBIDDEN'
9+
const ERROR_UNAUTHORISED = 'UNAUTHORISED'
10+
811
const ACL = function () {
912
this.resources = {}
1013

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

92-
return new Error('UNAUTHORISED')
95+
return new Error(ERROR_UNAUTHORISED)
9396
}
9497

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

109112
module.exports = new ACL()
110113
module.exports.ACL = ACL
114+
module.exports.ERROR_FORBIDDEN = ERROR_FORBIDDEN
115+
module.exports.ERROR_UNAUTHORISED = ERROR_UNAUTHORISED
111116
module.exports.access = access
112117
module.exports.client = client
113118
module.exports.role = role

dadi/lib/model/collections/create.js

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,34 @@ function create ({
5858
return document
5959
})
6060

61+
let {hooks} = this.settings
62+
let originalDocuments = documents
63+
64+
// If an ACL check is performed, this variable will contain the resulting
65+
// access matrix.
66+
let aclAccess
67+
6168
return this.validateAccess({
6269
client,
70+
documents,
6371
type: 'create'
64-
}).then(({schema}) => {
72+
}).then(({access, documents: newDocuments, fields, schema}) => {
6573
if (!validate) return
6674

75+
// Storing the access matrix in a variable that is global to the method.
76+
aclAccess = access
77+
78+
// This is now the filtered documents object, containing only the fields
79+
// which the client has access to.
80+
documents = newDocuments
81+
6782
return this.validator.validateDocuments({
6883
documents,
6984
schema
7085
}).catch(errors => {
71-
let error = this._createValidationError('Validation Failed', errors)
86+
let error = this._createValidationError('Validation Failed', errors, {
87+
originalDocuments
88+
})
7289

7390
return Promise.reject(error)
7491
})
@@ -113,16 +130,16 @@ function create ({
113130
return transformQueue
114131
}).then(documents => {
115132
// Run any `beforeCreate` hooks.
116-
if (this.settings.hooks && this.settings.hooks.beforeCreate) {
133+
if (hooks && hooks.beforeCreate) {
117134
return new Promise((resolve, reject) => {
118135
let processedDocuments = 0
119136

120137
documents.forEach((doc, docIndex) => {
121-
async.reduce(this.settings.hooks.beforeCreate, doc, (current, hookConfig, callback) => {
138+
async.reduce(hooks.beforeCreate, doc, (current, hookConfig, callback) => {
122139
let hook = new Hook(hookConfig, 'beforeCreate')
123140

124141
Promise.resolve(hook.apply(current, this.schema, this.name, req))
125-
.then((newDoc) => {
142+
.then(newDoc => {
126143
callback((newDoc === null) ? {} : null, newDoc)
127144
})
128145
.catch(err => {
@@ -151,34 +168,31 @@ function create ({
151168
schema: this.schema,
152169
settings: this.settings
153170
}).then(results => {
154-
let returnData = {
155-
results
156-
}
157-
158171
// Asynchronous search index.
159-
this.searchHandler.index(returnData.results)
172+
this.searchHandler.index(results)
160173

161174
// Run any `afterCreate` hooks.
162-
if (this.settings.hooks && (typeof this.settings.hooks.afterCreate === 'object')) {
163-
returnData.results.forEach(document => {
164-
this.settings.hooks.afterCreate.forEach((hookConfig, index) => {
165-
let hook = new Hook(this.settings.hooks.afterCreate[index], 'afterCreate')
175+
if (hooks && Array.isArray(hooks.afterCreate)) {
176+
results.forEach(document => {
177+
hooks.afterCreate.forEach((hookConfig, index) => {
178+
let hook = new Hook(hooks.afterCreate[index], 'afterCreate')
166179

167180
return hook.apply(document, this.schema, this.name)
168181
})
169182
})
170183
}
171184

172-
// Prepare result set for output.
173-
if (!rawOutput) {
174-
return this.formatForOutput(
175-
returnData.results,
176-
{
177-
composeOverride: compose
178-
}).then(results => ({results}))
185+
// If `rawOutput` is truthy, we don't need to worry about formatting
186+
// the result set for output. We return it as is.
187+
if (rawOutput) {
188+
return {results}
179189
}
180190

181-
return returnData
191+
return this.formatForOutput(results, {
192+
access: aclAccess && aclAccess.read,
193+
client,
194+
composeOverride: compose
195+
}).then(results => ({results}))
182196
})
183197
})
184198
}

dadi/lib/model/collections/get.js

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,20 @@ const logger = require('@dadi/logger')
2424
* Finds documents in the database, running any configured hooks
2525
* and formatting the result set for final output.
2626
*
27-
* @param {Object} client - client to check permissions for
28-
* @param {String} language - ISO code for the language to translate documents to
29-
* @param {Object} query - query to match documents against
30-
* @param {Object} options
31-
* @param {Object} req - request object to pass to hooks
27+
* @param {Object} client - client to check permissions for
28+
* @param {String} language - ISO code for the language to translate documents to
29+
* @param {Object} query - query to match documents against
30+
* @param {Object} options
31+
* @param {Boolean} rawOutput - whether to bypass formatting routine
32+
* @param {Object} req - request object to pass to hooks
3233
* @return {Promise<ResultSet>}
3334
*/
3435
function get ({
3536
client,
3637
language,
3738
query = {},
3839
options = {},
40+
rawOutput = false,
3941
req
4042
}) {
4143
// Is this a RESTful query by ID?
@@ -112,15 +114,19 @@ function get ({
112114

113115
return response
114116
}).then(({metadata, results}) => {
115-
return this.formatForOutput(
116-
results,
117-
{
118-
client,
119-
composeOverride: options.compose,
120-
language,
121-
urlFields: options.fields
122-
}
123-
).then(results => {
117+
let formatter = rawOutput
118+
? Promise.resolve(results)
119+
: this.formatForOutput(
120+
results,
121+
{
122+
client,
123+
composeOverride: options.compose,
124+
language,
125+
urlFields: options.fields
126+
}
127+
)
128+
129+
return formatter.then(results => {
124130
return {results, metadata}
125131
})
126132
})

dadi/lib/model/collections/update.js

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,26 @@ function update ({
7777
// Get a reference to the documents that will be updated.
7878
let updatedDocuments = []
7979

80+
// Removing internal API properties from the update object.
81+
if (removeInternalProperties) {
82+
update = this.removeInternalProperties(update)
83+
}
84+
85+
let {hooks} = this.settings
86+
87+
// If an ACL check is performed, this variable will contain the resulting
88+
// access matrix.
89+
let aclAccess
90+
8091
return this.validateAccess({
8192
client,
93+
documents: update,
8294
query,
8395
type: 'update'
84-
}).then(({query: aclQuery, schema}) => {
96+
}).then(({access, documents: newUpdate, query: aclQuery, schema}) => {
97+
aclAccess = access
8598
query = aclQuery
99+
update = newUpdate
86100

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

95-
// Removing internal API properties from the update object.
96-
if (removeInternalProperties) {
97-
update = this.removeInternalProperties(update)
98-
}
99-
100109
if (!validate) return
101110

102111
// Validating the query.
@@ -160,9 +169,9 @@ function update ({
160169
update = transformedUpdate
161170

162171
// Run any `beforeUpdate` hooks.
163-
if (this.settings.hooks && this.settings.hooks.beforeUpdate) {
172+
if (hooks && hooks.beforeUpdate) {
164173
return new Promise((resolve, reject) => {
165-
async.reduce(this.settings.hooks.beforeUpdate, update, (current, hookConfig, callback) => {
174+
async.reduce(hooks.beforeUpdate, update, (current, hookConfig, callback) => {
166175
let hook = new Hook(hookConfig, 'beforeUpdate')
167176

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

222231
return this.find({
223-
query: updatedDocumentsQuery,
224232
options: {
225233
compose: true
226-
}
234+
},
235+
query: updatedDocumentsQuery
227236
})
228237
}).then(data => {
229238
if (data.results.length === 0) {
230239
return data
231240
}
232241

233242
// Run any `afterUpdate` hooks.
234-
if (this.settings.hooks && (typeof this.settings.hooks.afterUpdate === 'object')) {
235-
this.settings.hooks.afterUpdate.forEach((hookConfig, index) => {
236-
let hook = new Hook(this.settings.hooks.afterUpdate[index], 'afterUpdate')
243+
if (hooks && Array.isArray(hooks.afterUpdate)) {
244+
hooks.afterUpdate.forEach((hookConfig, index) => {
245+
let hook = new Hook(hooks.afterUpdate[index], 'afterUpdate')
237246

238247
return hook.apply(data.results, this.schema, this.name)
239248
})
@@ -245,6 +254,7 @@ function update ({
245254
// Format result set for output.
246255
if (!rawOutput) {
247256
return this.formatForOutput(data.results, {
257+
access: aclAccess && aclAccess.read,
248258
client,
249259
composeOverride: compose
250260
}).then(results => {

0 commit comments

Comments
 (0)